feat: 为API Key添加模型限制功能

- 前端:在API Key创建和编辑表单中添加模型限制开关和标签输入
- 前端:支持动态添加/删除限制的模型列表
- 后端:更新API Key数据结构,新增enableModelRestriction和restrictedModels字段
- 后端:在中转请求时检查模型访问权限
- 修复:Enter键提交表单问题,使用@keydown.enter.prevent
- 优化:限制模型数据持久化,关闭开关时不清空数据

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-07-19 20:54:26 +08:00
parent f9933f7061
commit f962083752
9 changed files with 278 additions and 22 deletions

View File

@@ -117,7 +117,10 @@ const app = createApp({
tokenLimit: '',
description: '',
concurrencyLimit: '',
claudeAccountId: ''
claudeAccountId: '',
enableModelRestriction: false,
restrictedModels: [],
modelInput: ''
},
apiKeyModelStats: {}, // 存储每个key的模型统计数据
expandedApiKeys: {}, // 跟踪展开的API Keys
@@ -155,7 +158,10 @@ const app = createApp({
name: '',
tokenLimit: '',
concurrencyLimit: '',
claudeAccountId: ''
claudeAccountId: '',
enableModelRestriction: false,
restrictedModels: [],
modelInput: ''
},
// 账户
@@ -313,6 +319,20 @@ const app = createApp({
return this.apiKeys.filter(key => key.claudeAccountId === accountId).length;
},
// 添加限制模型
addRestrictedModel(form) {
const model = form.modelInput.trim();
if (model && !form.restrictedModels.includes(model)) {
form.restrictedModels.push(model);
form.modelInput = '';
}
},
// 移除限制模型
removeRestrictedModel(form, index) {
form.restrictedModels.splice(index, 1);
},
// Toast 通知方法
showToast(message, type = 'info', title = null, duration = 5000) {
const id = ++this.toastIdCounter;
@@ -1191,7 +1211,9 @@ const app = createApp({
tokenLimit: this.apiKeyForm.tokenLimit && this.apiKeyForm.tokenLimit.trim() ? parseInt(this.apiKeyForm.tokenLimit) : null,
description: this.apiKeyForm.description || '',
concurrencyLimit: this.apiKeyForm.concurrencyLimit && this.apiKeyForm.concurrencyLimit.trim() ? parseInt(this.apiKeyForm.concurrencyLimit) : 0,
claudeAccountId: this.apiKeyForm.claudeAccountId || null
claudeAccountId: this.apiKeyForm.claudeAccountId || null,
enableModelRestriction: this.apiKeyForm.enableModelRestriction,
restrictedModels: this.apiKeyForm.restrictedModels
})
});
@@ -1209,7 +1231,7 @@ const app = createApp({
// 关闭创建弹窗并清理表单
this.showCreateApiKeyModal = false;
this.apiKeyForm = { name: '', tokenLimit: '', description: '', concurrencyLimit: '', claudeAccountId: '' };
this.apiKeyForm = { name: '', tokenLimit: '', description: '', concurrencyLimit: '', claudeAccountId: '', enableModelRestriction: false, restrictedModels: [], modelInput: '' };
// 重新加载API Keys列表
await this.loadApiKeys();
@@ -1253,7 +1275,10 @@ const app = createApp({
name: key.name,
tokenLimit: key.tokenLimit || '',
concurrencyLimit: key.concurrencyLimit || '',
claudeAccountId: key.claudeAccountId || ''
claudeAccountId: key.claudeAccountId || '',
enableModelRestriction: key.enableModelRestriction || false,
restrictedModels: key.restrictedModels ? [...key.restrictedModels] : [],
modelInput: ''
};
this.showEditApiKeyModal = true;
},
@@ -1265,7 +1290,10 @@ const app = createApp({
name: '',
tokenLimit: '',
concurrencyLimit: '',
claudeAccountId: ''
claudeAccountId: '',
enableModelRestriction: false,
restrictedModels: [],
modelInput: ''
};
},
@@ -1281,7 +1309,9 @@ const app = createApp({
body: JSON.stringify({
tokenLimit: this.editApiKeyForm.tokenLimit && this.editApiKeyForm.tokenLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.tokenLimit) : 0,
concurrencyLimit: this.editApiKeyForm.concurrencyLimit && this.editApiKeyForm.concurrencyLimit.toString().trim() !== '' ? parseInt(this.editApiKeyForm.concurrencyLimit) : 0,
claudeAccountId: this.editApiKeyForm.claudeAccountId || null
claudeAccountId: this.editApiKeyForm.claudeAccountId || null,
enableModelRestriction: this.editApiKeyForm.enableModelRestriction,
restrictedModels: this.editApiKeyForm.restrictedModels
})
});