From 2d41a1b45dc58c1d14cb444468d5a507f7f113a3 Mon Sep 17 00:00:00 2001 From: tbphp Date: Wed, 23 Jul 2025 18:39:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=88=9B=E5=BB=BA?= =?UTF-8?q?=E5=88=86=E7=BB=84=E5=AD=97=E6=AE=B5=E5=A1=AB=E5=85=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/keys/GroupFormModal.vue | 100 +++++++++++++++++++-- web/src/components/keys/GroupList.vue | 12 +-- web/src/views/Keys.vue | 10 +++ 3 files changed, 107 insertions(+), 15 deletions(-) diff --git a/web/src/components/keys/GroupFormModal.vue b/web/src/components/keys/GroupFormModal.vue index 019dc40..287701d 100644 --- a/web/src/components/keys/GroupFormModal.vue +++ b/web/src/components/keys/GroupFormModal.vue @@ -86,6 +86,12 @@ const configOptions = ref([]); const channelTypesFetched = ref(false); const configOptionsFetched = ref(false); +// 跟踪用户是否已手动修改过字段(仅在新增模式下使用) +const userModifiedFields = ref({ + test_model: false, + upstream: false, +}); + // 根据渠道类型动态生成占位符提示 const testModelPlaceholder = computed(() => { switch (formData.channel_type) { @@ -183,21 +189,95 @@ watch( } ); +// 监听渠道类型变化,在新增模式下智能更新默认值 +watch( + () => formData.channel_type, + (_newChannelType, oldChannelType) => { + if (!props.group && oldChannelType) { + // 仅在新增模式且不是初始设置时处理 + // 检查测试模型是否应该更新(为空或是旧渠道类型的默认值) + if ( + !userModifiedFields.value.test_model || + formData.test_model === getOldDefaultTestModel(oldChannelType) + ) { + formData.test_model = testModelPlaceholder.value; + userModifiedFields.value.test_model = false; + } + + // 检查第一个上游地址是否应该更新 + if ( + formData.upstreams.length > 0 && + (!userModifiedFields.value.upstream || + formData.upstreams[0].url === getOldDefaultUpstream(oldChannelType)) + ) { + formData.upstreams[0].url = upstreamPlaceholder.value; + userModifiedFields.value.upstream = false; + } + } + } +); + +// 获取旧渠道类型的默认值(用于比较) +function getOldDefaultTestModel(channelType: string): string { + switch (channelType) { + case "openai": + return "gpt-4.1-nano"; + case "gemini": + return "gemini-2.0-flash-lite"; + case "anthropic": + return "claude-3-haiku-20240307"; + default: + return ""; + } +} + +function getOldDefaultUpstream(channelType: string): string { + switch (channelType) { + case "openai": + return "https://api.openai.com"; + case "gemini": + return "https://generativelanguage.googleapis.com"; + case "anthropic": + return "https://api.anthropic.com"; + default: + return ""; + } +} + // 重置表单 function resetForm() { + const isCreateMode = !props.group; + const defaultChannelType = "openai"; + + // 先设置渠道类型,这样 computed 属性能正确计算默认值 + formData.channel_type = defaultChannelType; + Object.assign(formData, { name: "", display_name: "", description: "", - upstreams: [{ url: "", weight: 1 }], - channel_type: "openai", + upstreams: [ + { + url: isCreateMode ? upstreamPlaceholder.value : "", + weight: 1, + }, + ], + channel_type: defaultChannelType, sort: 1, - test_model: "", + test_model: isCreateMode ? testModelPlaceholder.value : "", validation_endpoint: "", param_overrides: "", config: {}, configItems: [], }); + + // 重置用户修改状态追踪 + if (isCreateMode) { + userModifiedFields.value = { + test_model: false, + upstream: false, + }; + } } // 加载分组数据(编辑模式) @@ -466,11 +546,15 @@ async function handleSubmit() { - 用于验证API密钥有效性的模型名称。系统会使用这个模型发送测试请求来检查密钥是否可用 + 用于验证API密钥有效性的模型名称。系统会使用这个模型发送测试请求来检查密钥是否可用,请尽量使用轻量快速的模型 - +
- +
权重 diff --git a/web/src/components/keys/GroupList.vue b/web/src/components/keys/GroupList.vue index 3a0cfdb..2891341 100644 --- a/web/src/components/keys/GroupList.vue +++ b/web/src/components/keys/GroupList.vue @@ -15,6 +15,7 @@ interface Props { interface Emits { (e: "group-select", group: Group): void; (e: "refresh"): void; + (e: "refresh-and-select", groupId: number): void; } const props = withDefaults(defineProps(), { @@ -67,15 +68,8 @@ function handleGroupCreated(_group: Group) { } function handleSwitchToGroup(groupId: number) { - // 创建成功后,等待列表刷新完成,然后切换到新创建的分组 - emit("refresh"); - // 延迟选择新分组,确保列表已更新 - setTimeout(() => { - const newGroup = props.groups.find(g => g.id === groupId); - if (newGroup) { - emit("group-select", newGroup); - } - }, 100); + // 创建成功后,通知父组件刷新并切换到新创建的分组 + emit("refresh-and-select", groupId); } diff --git a/web/src/views/Keys.vue b/web/src/views/Keys.vue index fbc83a7..b5a6733 100644 --- a/web/src/views/Keys.vue +++ b/web/src/views/Keys.vue @@ -51,6 +51,15 @@ async function handleGroupRefresh() { } } +async function handleGroupRefreshAndSelect(targetGroupId: number) { + await loadGroups(); + // 刷新完成后,切换到指定的分组 + const targetGroup = groups.value.find(g => g.id === targetGroupId); + if (targetGroup) { + handleGroupSelect(targetGroup); + } +} + function handleGroupDelete(deletedGroup: Group) { // 从分组列表中移除已删除的分组 groups.value = groups.value.filter(g => g.id !== deletedGroup.id); @@ -71,6 +80,7 @@ function handleGroupDelete(deletedGroup: Group) { :loading="loading" @group-select="handleGroupSelect" @refresh="handleGroupRefresh" + @refresh-and-select="handleGroupRefreshAndSelect" />