From 5ed3695c4889140ca70cd43b8439c76ddc9158e3 Mon Sep 17 00:00:00 2001 From: AAEE86 Date: Sun, 11 Jan 2026 04:12:29 +0800 Subject: [PATCH] feat(ui): add inline editing for API key rate multipliers Add click-to-edit functionality for rate multipliers in the provider detail drawer. Users can now click on any multiplier value to edit it inline, with support for keyboard shortcuts (Enter to save, Escape to cancel) and automatic focus management. The changes are immediately saved to the backend and reflected in the UI. --- .../components/ProviderDetailDrawer.vue | 122 +++++++++++++++++- 1 file changed, 120 insertions(+), 2 deletions(-) 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