diff --git a/frontend/src/features/providers/components/ProviderDetailDrawer.vue b/frontend/src/features/providers/components/ProviderDetailDrawer.vue index a652f19..edd1a0d 100644 --- a/frontend/src/features/providers/components/ProviderDetailDrawer.vue +++ b/frontend/src/features/providers/components/ProviderDetailDrawer.vue @@ -325,10 +325,31 @@ class="text-muted-foreground/40" >/ - {{ API_FORMAT_SHORT[format] || format }} {{ getKeyRateMultiplier(key, format) }}x{{ getFormatProbeCountdown(key, format) }} + {{ API_FORMAT_SHORT[format] || format }} + {{ getKeyRateMultiplier(key, format) }}x + + {{ getFormatProbeCountdown(key, format) }} - | {{ key.rate_limit }}rpm @@ -765,6 +786,13 @@ const editingPriorityValue = ref(0) const priorityInputRef = ref(null) const prioritySaving = ref(false) +// 点击编辑倍率相关状态 +const editingMultiplierKey = ref(null) +const editingMultiplierFormat = ref(null) +const editingMultiplierValue = ref(1.0) +const multiplierInputRef = ref(null) +const multiplierSaving = ref(false) + // 任意模态窗口打开时,阻止抽屉被误关闭 const hasBlockingDialogOpen = computed(() => endpointDialogOpen.value || @@ -1338,6 +1366,96 @@ async function savePriority(key: EndpointAPIKey) { } } +// ===== 点击编辑倍率 ===== +function startEditMultiplier(key: EndpointAPIKey, format: string) { + editingMultiplierKey.value = key.id + editingMultiplierFormat.value = format + editingMultiplierValue.value = getKeyRateMultiplier(key, format) + multiplierSaving.value = false + nextTick(() => { + const input = Array.isArray(multiplierInputRef.value) ? multiplierInputRef.value[0] : multiplierInputRef.value + input?.focus() + input?.select() + }) +} + +function cancelEditMultiplier() { + editingMultiplierKey.value = null + editingMultiplierFormat.value = null + multiplierSaving.value = false +} + +function handleMultiplierKeydown(e: KeyboardEvent, key: EndpointAPIKey, format: string) { + if (e.key === 'Enter') { + e.preventDefault() + e.stopPropagation() + if (!multiplierSaving.value) { + multiplierSaving.value = true + saveMultiplier(key, format) + } + } else if (e.key === 'Escape') { + e.preventDefault() + cancelEditMultiplier() + } +} + +function handleMultiplierBlur(key: EndpointAPIKey, format: string) { + if (multiplierSaving.value) return + saveMultiplier(key, format) +} + +async function saveMultiplier(key: EndpointAPIKey, format: string) { + // 防止重复调用 + if (multiplierSaving.value) return + multiplierSaving.value = true + + const keyId = editingMultiplierKey.value + const newMultiplier = parseFloat(String(editingMultiplierValue.value)) + + // 验证输入有效性 + if (!keyId || isNaN(newMultiplier)) { + showError('请输入有效的倍率值') + cancelEditMultiplier() + return + } + + // 验证合理范围 + if (newMultiplier <= 0 || newMultiplier > 100) { + showError('倍率必须在 0.01 到 100 之间') + cancelEditMultiplier() + return + } + + // 如果倍率没有变化,直接取消编辑(使用精度容差比较浮点数) + const currentMultiplier = getKeyRateMultiplier(key, format) + if (Math.abs(currentMultiplier - newMultiplier) < 0.0001) { + cancelEditMultiplier() + return + } + + cancelEditMultiplier() + + try { + // 构建 rate_multipliers 对象 + const rateMultipliers = { ...(key.rate_multipliers || {}) } + rateMultipliers[format] = newMultiplier + + await updateProviderKey(keyId, { rate_multipliers: rateMultipliers }) + showSuccess('倍率已更新') + + // 更新本地数据 + const keyToUpdate = providerKeys.value.find(k => k.id === keyId) + if (keyToUpdate) { + keyToUpdate.rate_multipliers = rateMultipliers + } + emit('refresh') + } catch (err: any) { + showError(err.response?.data?.detail || '更新倍率失败', '错误') + } finally { + multiplierSaving.value = false + } +} + // ===== 密钥列表拖拽排序 ===== function handleKeyDragStart(event: DragEvent, index: number) { keyDragState.value.isDragging = true