mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat(api-keys): 添加模型筛选功能
This commit is contained in:
@@ -43,7 +43,7 @@
|
||||
:key="option.value"
|
||||
class="flex cursor-pointer items-center gap-2 whitespace-nowrap px-3 py-2 text-sm transition-colors duration-150"
|
||||
:class="[
|
||||
option.value === modelValue
|
||||
isSelected(option.value)
|
||||
? 'bg-blue-50 font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400'
|
||||
: 'text-gray-700 hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700'
|
||||
]"
|
||||
@@ -52,7 +52,7 @@
|
||||
<i v-if="option.icon" :class="['fas', option.icon, 'text-xs']"></i>
|
||||
<span>{{ option.label }}</span>
|
||||
<i
|
||||
v-if="option.value === modelValue"
|
||||
v-if="isSelected(option.value)"
|
||||
class="fas fa-check ml-auto pl-3 text-xs text-blue-600"
|
||||
></i>
|
||||
</div>
|
||||
@@ -68,7 +68,7 @@ import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: [String, Number],
|
||||
type: [String, Number, Array],
|
||||
default: ''
|
||||
},
|
||||
options: {
|
||||
@@ -86,6 +86,10 @@ const props = defineProps({
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: 'text-gray-500'
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
@@ -96,7 +100,18 @@ const triggerRef = ref(null)
|
||||
const dropdownRef = ref(null)
|
||||
const dropdownStyle = ref({})
|
||||
|
||||
const isSelected = (value) => {
|
||||
if (props.multiple) {
|
||||
return Array.isArray(props.modelValue) && props.modelValue.includes(value)
|
||||
}
|
||||
return props.modelValue === value
|
||||
}
|
||||
|
||||
const selectedLabel = computed(() => {
|
||||
if (props.multiple) {
|
||||
const count = Array.isArray(props.modelValue) ? props.modelValue.length : 0
|
||||
return count > 0 ? `已选 ${count} 个` : ''
|
||||
}
|
||||
const selected = props.options.find((opt) => opt.value === props.modelValue)
|
||||
return selected ? selected.label : ''
|
||||
})
|
||||
@@ -114,9 +129,21 @@ const closeDropdown = () => {
|
||||
}
|
||||
|
||||
const selectOption = (option) => {
|
||||
emit('update:modelValue', option.value)
|
||||
emit('change', option.value)
|
||||
closeDropdown()
|
||||
if (props.multiple) {
|
||||
const current = Array.isArray(props.modelValue) ? [...props.modelValue] : []
|
||||
const idx = current.indexOf(option.value)
|
||||
if (idx >= 0) {
|
||||
current.splice(idx, 1)
|
||||
} else {
|
||||
current.push(option.value)
|
||||
}
|
||||
emit('update:modelValue', current)
|
||||
emit('change', current)
|
||||
} else {
|
||||
emit('update:modelValue', option.value)
|
||||
emit('change', option.value)
|
||||
closeDropdown()
|
||||
}
|
||||
}
|
||||
|
||||
const updateDropdownPosition = () => {
|
||||
|
||||
@@ -116,6 +116,29 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型筛选器 -->
|
||||
<div class="group relative min-w-[140px]">
|
||||
<div
|
||||
class="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-orange-500 to-amber-500 opacity-0 blur transition duration-300 group-hover:opacity-20"
|
||||
></div>
|
||||
<div class="relative">
|
||||
<CustomDropdown
|
||||
v-model="selectedModels"
|
||||
icon="fa-cube"
|
||||
icon-color="text-orange-500"
|
||||
:options="modelOptions"
|
||||
placeholder="所有模型"
|
||||
:multiple="true"
|
||||
/>
|
||||
<span
|
||||
v-if="selectedModels.length > 0"
|
||||
class="absolute -right-2 -top-2 z-10 flex h-5 w-5 items-center justify-center rounded-full bg-orange-500 text-xs text-white shadow-sm"
|
||||
>
|
||||
{{ selectedModels.length }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 搜索模式与搜索框 -->
|
||||
<div class="flex min-w-[240px] flex-col gap-2 sm:flex-row sm:items-center">
|
||||
<div class="sm:w-44">
|
||||
@@ -2220,6 +2243,10 @@ const selectedApiKeyForDetail = ref(null)
|
||||
const selectedTagFilter = ref('')
|
||||
const availableTags = ref([])
|
||||
|
||||
// 模型筛选相关
|
||||
const selectedModels = ref([])
|
||||
const availableModels = ref([])
|
||||
|
||||
// 搜索相关
|
||||
const searchKeyword = ref('')
|
||||
const searchMode = ref('apiKey')
|
||||
@@ -2236,6 +2263,14 @@ const tagOptions = computed(() => {
|
||||
return options
|
||||
})
|
||||
|
||||
const modelOptions = computed(() => {
|
||||
return availableModels.value.map((model) => ({
|
||||
value: model,
|
||||
label: model,
|
||||
icon: 'fa-cube'
|
||||
}))
|
||||
})
|
||||
|
||||
const selectedTagCount = computed(() => {
|
||||
if (!selectedTagFilter.value) return 0
|
||||
return apiKeys.value.filter((key) => key.tags && key.tags.includes(selectedTagFilter.value))
|
||||
@@ -2474,6 +2509,18 @@ const loadAccounts = async (forceRefresh = false) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 加载已使用的模型列表
|
||||
const loadUsedModels = async () => {
|
||||
try {
|
||||
const data = await apiClient.get('/admin/api-keys/used-models')
|
||||
if (data.success) {
|
||||
availableModels.value = data.data || []
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load used models:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 加载API Keys(使用后端分页)
|
||||
const loadApiKeys = async (clearStatsCache = true) => {
|
||||
apiKeysLoading.value = true
|
||||
@@ -2502,6 +2549,11 @@ const loadApiKeys = async (clearStatsCache = true) => {
|
||||
params.set('tag', selectedTagFilter.value)
|
||||
}
|
||||
|
||||
// 模型筛选参数
|
||||
if (selectedModels.value.length > 0) {
|
||||
params.set('models', selectedModels.value.join(','))
|
||||
}
|
||||
|
||||
// 排序参数(支持费用排序)
|
||||
const validSortFields = [
|
||||
'name',
|
||||
@@ -4711,6 +4763,12 @@ watch(selectedTagFilter, () => {
|
||||
loadApiKeys(false)
|
||||
})
|
||||
|
||||
// 监听模型筛选变化
|
||||
watch(selectedModels, () => {
|
||||
currentPage.value = 1
|
||||
loadApiKeys(false)
|
||||
})
|
||||
|
||||
// 监听排序变化,重新加载数据
|
||||
watch([apiKeysSortBy, apiKeysSortOrder], () => {
|
||||
loadApiKeys(false)
|
||||
@@ -4745,7 +4803,7 @@ onMounted(async () => {
|
||||
fetchCostSortStatus()
|
||||
|
||||
// 先加载 API Keys(优先显示列表)
|
||||
await Promise.all([clientsStore.loadSupportedClients(), loadApiKeys()])
|
||||
await Promise.all([clientsStore.loadSupportedClients(), loadApiKeys(), loadUsedModels()])
|
||||
|
||||
// 初始化全选状态
|
||||
updateSelectAllState()
|
||||
|
||||
Reference in New Issue
Block a user