11<template >
22 <div class =" flex min-h-0 flex-1 flex-col" >
33 <div class =" card flex min-h-0 flex-1 flex-col overflow-hidden" >
4- <div
5- v-if =" currentPageIps.length > 0"
6- class =" flex flex-shrink-0 items-center justify-end gap-2 border-b border-gray-200 px-4 py-2 dark:border-dark-700"
7- >
8- <span v-if =" pendingIpCount > 0" class =" text-xs text-gray-500 dark:text-gray-400" >
9- {{ t('usage.ipGeo.pending', { count: pendingIpCount }) }}
10- </span >
11- <button
12- type =" button"
13- class =" inline-flex items-center gap-1 rounded px-2 py-1 text-xs font-medium text-primary-600 transition-colors hover:bg-primary-50 disabled:cursor-not-allowed disabled:opacity-50 dark:text-primary-400 dark:hover:bg-primary-900/30"
14- :disabled =" ipGeoBatchLoading || pendingIpCount === 0"
15- @click =" handleBatchFetchIpGeo"
16- >
17- {{ ipGeoBatchLoading ? t('usage.ipGeo.batchFetching') : t('usage.ipGeo.batchFetch') }}
18- </button >
19- </div >
4+ <IpGeoBatchToolbar :ips =" rows .map ((r ) => r .client_ip )" @failed =" emit (' ipGeoBatchFailed' )" />
205
216 <DataTable
227 :columns =" columns "
@@ -143,8 +128,15 @@ import EmptyState from '@/components/common/EmptyState.vue'
143128import Pagination from ' @/components/common/Pagination.vue'
144129import UserErrorDetailModal from ' @/components/user/UserErrorDetailModal.vue'
145130import IpGeoCell from ' @/components/common/IpGeoCell.vue'
131+ import IpGeoBatchToolbar from ' @/components/common/IpGeoBatchToolbar.vue'
146132import { formatDateTime } from ' @/utils/format'
147- import { fetchBatch , getEntry } from ' @/utils/ipGeoLookup'
133+ import {
134+ mapErrorSortKey ,
135+ numericRequestTypeKind ,
136+ requestTypeBadgeClass ,
137+ requestTypeLabelKey ,
138+ statusCodeBadgeClass ,
139+ } from ' @/utils/errorBadges'
148140import type { UserErrorRequest } from ' @/types'
149141import type { Column } from ' @/components/common/types'
150142
@@ -165,9 +157,8 @@ const emit = defineEmits<{
165157 (e : ' sort' , sortBy : string , sortOrder : ' asc' | ' desc' ): void
166158}>()
167159
168- // 列 key → 后端 sort_by:status 列实际按 status_code 排序
169160function onSort(key : string , order : ' asc' | ' desc' ) {
170- emit (' sort' , key === ' status ' ? ' status_code ' : key , order )
161+ emit (' sort' , mapErrorSortKey ( key ) , order )
171162}
172163
173164const { t } = useI18n ()
@@ -195,37 +186,10 @@ const columns = computed<Column[]>(() =>
195186 : allColumns .value
196187)
197188
198- // 类型徽章:request_type 1/2/3 → 同步/流式/WS;缺失时按 stream 布尔回退。
199- // 配色对齐 UsageTable getRequestTypeBadgeClass(stream 蓝、sync 灰、ws 紫)。
200189function requestTypeBadge(row : UserErrorRequest ): { label: string ; className: string } | null {
201- const rt = row .request_type ?? (row .stream == null ? 0 : row .stream ? 2 : 1 )
202- if (rt === 3 ) return { label: t (' usage.ws' ), className: ' bg-violet-100 text-violet-800 dark:bg-violet-900 dark:text-violet-200' }
203- if (rt === 2 ) return { label: t (' usage.stream' ), className: ' bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200' }
204- if (rt === 1 ) return { label: t (' usage.sync' ), className: ' bg-gray-100 text-gray-800 dark:bg-dark-700 dark:text-gray-200' }
205- return null
206- }
207-
208- const ipGeoBatchLoading = ref (false )
209-
210- const currentPageIps = computed (() =>
211- Array .from (new Set (props .rows .map ((row ) => row .client_ip ).filter ((ip ): ip is string => Boolean (ip ))))
212- )
213-
214- const pendingIpCount = computed (() =>
215- currentPageIps .value .filter ((ip ) => {
216- const status = getEntry (ip ).status
217- return status === ' idle' || status === ' error'
218- }).length
219- )
220-
221- const handleBatchFetchIpGeo = async () => {
222- ipGeoBatchLoading .value = true
223- try {
224- const ok = await fetchBatch (currentPageIps .value )
225- if (! ok ) emit (' ipGeoBatchFailed' )
226- } finally {
227- ipGeoBatchLoading .value = false
228- }
190+ const kind = numericRequestTypeKind (row .request_type , row .stream )
191+ if (! kind ) return null
192+ return { label: t (requestTypeLabelKey (kind )), className: requestTypeBadgeClass (kind ) }
229193}
230194
231195const showDetail = ref (false )
@@ -236,11 +200,5 @@ function openDetail(id: number) {
236200 showDetail .value = true
237201}
238202
239- // 状态码配色对齐管理端错误表(OpsErrorLogTable.getStatusClass)
240- function statusClass(code : number ) {
241- if (code >= 500 ) return ' bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
242- if (code === 429 ) return ' bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200'
243- if (code >= 400 ) return ' bg-amber-100 text-amber-800 dark:bg-amber-900 dark:text-amber-200'
244- return ' bg-gray-100 text-gray-800 dark:bg-dark-700 dark:text-gray-200'
245- }
203+ const statusClass = statusCodeBadgeClass
246204 </script >
0 commit comments