feat: /keys页面支持表单筛选

This commit is contained in:
shaw
2026-03-04 11:29:31 +08:00
parent 46ea9170cb
commit ba6de4c4d4
17 changed files with 133 additions and 18 deletions

View File

@@ -10,18 +10,20 @@ import type { ApiKey, CreateApiKeyRequest, UpdateApiKeyRequest, PaginatedRespons
* List all API keys for current user
* @param page - Page number (default: 1)
* @param pageSize - Items per page (default: 10)
* @param filters - Optional filter parameters
* @param options - Optional request options
* @returns Paginated list of API keys
*/
export async function list(
page: number = 1,
pageSize: number = 10,
filters?: { search?: string; status?: string; group_id?: number | string },
options?: {
signal?: AbortSignal
}
): Promise<PaginatedResponse<ApiKey>> {
const { data } = await apiClient.get<PaginatedResponse<ApiKey>>('/keys', {
params: { page, page_size: pageSize },
params: { page, page_size: pageSize, ...filters },
signal: options?.signal
})
return data

View File

@@ -444,6 +444,9 @@ export default {
keys: {
title: 'API Keys',
description: 'Manage your API keys and access tokens',
searchPlaceholder: 'Search name or key...',
allGroups: 'All Groups',
allStatus: 'All Status',
createKey: 'Create API Key',
editKey: 'Edit API Key',
deleteKey: 'Delete API Key',

View File

@@ -445,6 +445,9 @@ export default {
keys: {
title: 'API 密钥',
description: '管理您的 API 密钥和访问令牌',
searchPlaceholder: '搜索名称或Key...',
allGroups: '全部分组',
allStatus: '全部状态',
createKey: '创建密钥',
editKey: '编辑密钥',
deleteKey: '删除密钥',

View File

@@ -1,6 +1,29 @@
<template>
<AppLayout>
<TablePageLayout>
<template #filters>
<div class="flex flex-wrap items-center gap-3">
<SearchInput
v-model="filterSearch"
:placeholder="t('keys.searchPlaceholder')"
class="w-full sm:w-64"
@search="onFilterChange"
/>
<Select
:model-value="filterGroupId"
class="w-40"
:options="groupFilterOptions"
@update:model-value="onGroupFilterChange"
/>
<Select
:model-value="filterStatus"
class="w-40"
:options="statusFilterOptions"
@update:model-value="onStatusFilterChange"
/>
</div>
</template>
<template #actions>
<div class="flex justify-end gap-3">
<button
@@ -985,6 +1008,7 @@ import TablePageLayout from '@/components/layout/TablePageLayout.vue'
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
import EmptyState from '@/components/common/EmptyState.vue'
import Select from '@/components/common/Select.vue'
import SearchInput from '@/components/common/SearchInput.vue'
import Icon from '@/components/icons/Icon.vue'
import UseKeyModal from '@/components/keys/UseKeyModal.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
@@ -1042,6 +1066,11 @@ const pagination = ref({
pages: 0
})
// Filter state
const filterSearch = ref('')
const filterStatus = ref('')
const filterGroupId = ref<string | number>('')
const showCreateModal = ref(false)
const showEditModal = ref(false)
const showDeleteDialog = ref(false)
@@ -1116,6 +1145,36 @@ const statusOptions = computed(() => [
{ value: 'inactive', label: t('common.inactive') }
])
// Filter dropdown options
const groupFilterOptions = computed(() => [
{ value: '', label: t('keys.allGroups') },
{ value: 0, label: t('keys.noGroup') },
...groups.value.map((g) => ({ value: g.id, label: g.name }))
])
const statusFilterOptions = computed(() => [
{ value: '', label: t('keys.allStatus') },
{ value: 'active', label: t('keys.status.active') },
{ value: 'inactive', label: t('keys.status.inactive') },
{ value: 'quota_exhausted', label: t('keys.status.quota_exhausted') },
{ value: 'expired', label: t('keys.status.expired') }
])
const onFilterChange = () => {
pagination.value.page = 1
loadApiKeys()
}
const onGroupFilterChange = (value: string | number | boolean | null) => {
filterGroupId.value = value as string | number
onFilterChange()
}
const onStatusFilterChange = (value: string | number | boolean | null) => {
filterStatus.value = value as string
onFilterChange()
}
// Convert groups to Select options format with rate multiplier and subscription type
const groupOptions = computed(() =>
groups.value.map((group) => ({
@@ -1157,7 +1216,13 @@ const loadApiKeys = async () => {
const { signal } = controller
loading.value = true
try {
const response = await keysAPI.list(pagination.value.page, pagination.value.page_size, {
// Build filters
const filters: { search?: string; status?: string; group_id?: number | string } = {}
if (filterSearch.value) filters.search = filterSearch.value
if (filterStatus.value) filters.status = filterStatus.value
if (filterGroupId.value !== '') filters.group_id = filterGroupId.value
const response = await keysAPI.list(pagination.value.page, pagination.value.page_size, filters, {
signal
})
if (signal.aborted) return