mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-11-01 00:27:30 +00:00
feat(usage): 错误请求对齐用量明细(UI/排序/筛选/列设置)
错误请求列表(/admin/usage 错误 tab、Ops 弹窗、用户端 /usage 错误 tab) 对齐用量明细的交互与信息密度。Squash of: - feat(usage): 错误请求全面对齐用量明细(UI/列序/排序/筛选/列设置/新列) - refactor(usage): 错误表提取共享徽章工具与 IP 批量工具条,后端排序解析归并 SetSort - feat(usage): /admin/usage 错误请求新增分类过滤 - fix(ops): 错误列表 phase=upstream 过滤生效,守卫豁免改为显式 opt-in - fix(ops): 错误列表用户列回退显示已删除 KEY 所有者 - fix(usage): 错误请求状态码排序对齐展示/过滤,筛选项改固定常用码
This commit is contained in:
@@ -36,6 +36,8 @@
|
||||
v-for="(row, index) in sortedData"
|
||||
:key="resolveRowKey(row, index)"
|
||||
class="rounded-lg border border-gray-200 bg-white p-4 dark:border-dark-700 dark:bg-dark-900"
|
||||
:class="{ 'cursor-pointer': clickableRows }"
|
||||
@click="clickableRows && emit('rowClick', row)"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
@@ -166,6 +168,8 @@
|
||||
:data-index="virtualRow.index"
|
||||
:ref="measureElement"
|
||||
class="hover:bg-gray-50 dark:hover:bg-dark-800"
|
||||
:class="{ 'cursor-pointer': clickableRows }"
|
||||
@click="clickableRows && emit('rowClick', sortedData[virtualRow.index])"
|
||||
>
|
||||
<td
|
||||
v-for="(column, colIndex) in columns"
|
||||
@@ -214,6 +218,7 @@ const isDesktopViewport = ref(
|
||||
|
||||
const emit = defineEmits<{
|
||||
sort: [key: string, order: 'asc' | 'desc']
|
||||
rowClick: [row: any]
|
||||
}>()
|
||||
|
||||
// 表格容器引用
|
||||
@@ -381,6 +386,8 @@ interface Props {
|
||||
* will emit 'sort' events instead of performing client-side sorting.
|
||||
*/
|
||||
serverSideSort?: boolean
|
||||
/** Emit 'rowClick' on row/card click and show pointer cursor (interactive cells should @click.stop) */
|
||||
clickableRows?: boolean
|
||||
/** Estimated row height in px for the virtualizer (default 56) */
|
||||
estimateRowHeight?: number
|
||||
/** Number of rows to render beyond the visible area (default 5) */
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="uniqueIps.length > 0"
|
||||
class="flex flex-shrink-0 items-center justify-end gap-2 border-b border-gray-200 px-4 py-2 dark:border-dark-700"
|
||||
>
|
||||
<span v-if="pendingCount > 0" class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('usage.ipGeo.pending', { count: pendingCount }) }}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
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"
|
||||
:disabled="loading || pendingCount === 0"
|
||||
@click="run"
|
||||
>
|
||||
{{ loading ? t('usage.ipGeo.batchFetching') : t('usage.ipGeo.batchFetch') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { fetchBatch, getEntry } from '@/utils/ipGeoLookup'
|
||||
|
||||
// 当前页 IP 批量地理查询工具条:传入原始 IP 列表(可含空值),内部去重;
|
||||
// 无 IP 时自身不渲染。批量失败 emit failed,由使用方弹提示。
|
||||
const props = defineProps<{
|
||||
ips: Array<string | null | undefined>
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'failed'): void
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const uniqueIps = computed(() =>
|
||||
Array.from(new Set(props.ips.filter((ip): ip is string => Boolean(ip))))
|
||||
)
|
||||
|
||||
const pendingCount = computed(() =>
|
||||
uniqueIps.value.filter((ip) => {
|
||||
const status = getEntry(ip).status
|
||||
return status === 'idle' || status === 'error'
|
||||
}).length
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const run = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const ok = await fetchBatch(uniqueIps.value)
|
||||
if (!ok) emit('failed')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
Reference in New Issue
Block a user