feat: Provider 摘要 API 改为服务端分页,支持搜索和筛选

- 后端 /summary 接口新增 page/page_size/search/status/api_format/model_id 参数
- 新增 ProviderSummaryPageResponse 分页响应模型
- 前端 useProviderFilters 从客户端筛选改为构建服务端查询参数
- ProviderManagement 通过 watch queryParams 实现分页/筛选联动,搜索 debounce 300ms
- PriorityManagementDialog 改为对话框打开时自行加载全量 providers
- 其他使用方(StandaloneKeyFormDialog/UserFormDialog/ReplayDialog/ModelManagement)适配新接口
This commit is contained in:
fawney19
2026-03-09 12:45:54 +08:00
parent 40736a8334
commit 0046123e22
10 changed files with 182 additions and 151 deletions

View File

@@ -1168,7 +1168,7 @@ async function ensureProviderOptions() {
providerOptionsRequest = (async () => {
try {
loadingProviderOptions.value = true
providerOptions.value = await getProvidersSummary()
providerOptions.value = (await getProvidersSummary({ page_size: 9999 })).items
} catch (err: unknown) {
const message = parseApiError(err, '加载 Provider 列表失败')
showError(message, '错误')
@@ -1494,7 +1494,7 @@ async function refreshData() {
async function loadProviders() {
const requestId = ++providersRequestId
try {
const nextProviders = await getProvidersSummary()
const nextProviders = (await getProvidersSummary({ page_size: 9999 })).items
if (requestId !== providersRequestId) return
providers.value = nextProviders
} catch (err: unknown) {

View File

@@ -36,7 +36,7 @@
<!-- 空状态 -->
<div
v-else-if="filteredProviders.length === 0"
v-else-if="providers.length === 0"
class="flex flex-col items-center justify-center py-16 text-center"
>
<div class="text-muted-foreground mb-2">
@@ -87,7 +87,7 @@
</TableHeader>
<TableBody>
<ProviderTableRow
v-for="provider in paginatedProviders"
v-for="provider in providers"
:key="provider.id"
:provider="provider"
:editing-description-id="editingDescriptionId"
@@ -118,11 +118,11 @@
<!-- 移动端卡片列表 -->
<div
v-if="!loading && filteredProviders.length > 0"
v-if="!loading && providers.length > 0"
class="xl:hidden divide-y divide-border/40"
>
<ProviderMobileCard
v-for="provider in paginatedProviders"
v-for="provider in providers"
:key="provider.id"
:provider="provider"
:editing-description-id="editingDescriptionId"
@@ -146,9 +146,9 @@
<!-- 分页 -->
<Pagination
v-if="!loading && filteredProviders.length > 0"
v-if="!loading && total > 0"
:current="currentPage"
:total="filteredProviders.length"
:total="total"
:page-size="pageSize"
cache-key="provider-management-page-size"
@update:current="currentPage = $event"
@@ -168,7 +168,6 @@
<PriorityManagementDialog
v-model="priorityDialogOpen"
:providers="providers"
@saved="handlePrioritySaved"
/>
@@ -190,7 +189,7 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import Button from '@/components/ui/button.vue'
import Card from '@/components/ui/card.vue'
import Table from '@/components/ui/table.vue'
@@ -247,13 +246,12 @@ const {
apiFormatFilters,
modelFilters,
hasActiveFilters,
filteredProviders,
currentPage,
pageSize,
paginatedProviders,
total,
queryParams,
resetFilters,
} = useProviderFilters(
() => providers.value,
() => globalModels.value,
)
@@ -349,14 +347,15 @@ async function loadGlobalModelList() {
}
}
// 加载提供商列表
// 加载提供商列表(服务端分页)
async function loadProviders() {
const requestId = ++providersRequestId
loading.value = true
try {
const nextProviders = await getProvidersSummary()
const response = await getProvidersSummary(queryParams.value)
if (requestId !== providersRequestId) return
providers.value = nextProviders
providers.value = response.items
total.value = response.total
// 异步加载配置了 ops 的 provider 的余额数据
loadBalances(providers.value)
} catch (err: unknown) {
@@ -369,6 +368,24 @@ async function loadProviders() {
}
}
// 分页/筛选/搜索变化时重新加载
let debounceTimer: ReturnType<typeof setTimeout> | null = null
watch(queryParams, (newParams, oldParams) => {
if (debounceTimer) clearTimeout(debounceTimer)
// 搜索输入 debounce 300ms其他变化立即执行
const isSearchOnly = newParams.search !== oldParams?.search &&
newParams.page === oldParams?.page &&
newParams.page_size === oldParams?.page_size &&
newParams.status === oldParams?.status &&
newParams.api_format === oldParams?.api_format &&
newParams.model_id === oldParams?.model_id
if (isSearchOnly) {
debounceTimer = setTimeout(loadProviders, 300)
} else {
loadProviders()
}
}, { deep: true })
// 使用复用的行点击逻辑
const { handleMouseDown, shouldTriggerRowClick } = useRowClick()
@@ -508,6 +525,7 @@ onMounted(() => {
})
onUnmounted(() => {
if (debounceTimer) clearTimeout(debounceTimer)
document.removeEventListener('click', handleGlobalClick, true)
stopTick()
})