From 7e1a9daa6b8ece2e65bb1e6c6321dc3ed762cdf4 Mon Sep 17 00:00:00 2001 From: shaw Date: Fri, 8 Aug 2025 14:14:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E7=95=8C=E9=9D=A2=E5=8A=9F=E8=83=BD=E5=92=8C=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 API Key 窗口倒计时组件 (WindowCountdown) - 添加自定义下拉菜单组件 (CustomDropdown) - 优化账户和 API Key 管理界面交互 - 改进教程页面布局和说明文字 - 完善账户状态显示和错误处理 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/routes/admin.js | 11 + .../src/components/accounts/AccountForm.vue | 4 +- .../components/apikeys/CreateApiKeyModal.vue | 88 ++++-- .../components/apikeys/EditApiKeyModal.vue | 29 +- .../components/apikeys/WindowCountdown.vue | 254 ++++++++++++++++++ .../src/components/common/CustomDropdown.vue | 219 +++++++++++++++ web/admin-spa/src/views/AccountsView.vue | 104 +++++-- web/admin-spa/src/views/ApiKeysView.vue | 175 ++++++++++-- web/admin-spa/src/views/TutorialView.vue | 187 +++++-------- 9 files changed, 881 insertions(+), 190 deletions(-) create mode 100644 web/admin-spa/src/components/apikeys/WindowCountdown.vue create mode 100644 web/admin-spa/src/components/common/CustomDropdown.vue diff --git a/src/routes/admin.js b/src/routes/admin.js index 892b67ce..7db8efb9 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -620,6 +620,7 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { concurrencyLimit, rateLimitWindow, rateLimitRequests, + isActive, claudeAccountId, claudeConsoleAccountId, geminiAccountId, @@ -726,6 +727,7 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { if (expiresAt === null) { // null 表示永不过期 updates.expiresAt = null + updates.isActive = true } else { // 验证日期格式 const expireDate = new Date(expiresAt) @@ -733,6 +735,7 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { return res.status(400).json({ error: 'Invalid expiration date format' }) } updates.expiresAt = expiresAt + updates.isActive = expireDate > new Date() // 如果过期时间在当前时间之后,则设置为激活状态 } } @@ -756,6 +759,14 @@ router.put('/api-keys/:keyId', authenticateAdmin, async (req, res) => { updates.tags = tags } + // 处理活跃/禁用状态状态, 放在过期处理后,以确保后续增加禁用key功能 + if (isActive !== undefined) { + if (typeof isActive !== 'boolean') { + return res.status(400).json({ error: 'isActive must be a boolean' }) + } + updates.isActive = isActive + } + await apiKeyService.updateApiKey(keyId, updates) logger.success(`📝 Admin updated API key: ${keyId}`) diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue index a4b82369..9a499a69 100644 --- a/web/admin-spa/src/components/accounts/AccountForm.vue +++ b/web/admin-spa/src/components/accounts/AccountForm.vue @@ -89,7 +89,7 @@ >
-
-
+
- -
+ +
{{ model }} - + 暂无限制的模型
-
- - +
+ +
+ + + 所有常用模型已在限制列表中 + +
+ + +
+ + +
-

例如:claude-opus-4-20250514

+

+ 设置此API Key无法访问的模型,例如:claude-opus-4-20250514 +

@@ -769,6 +792,21 @@ const removeRestrictedModel = (index) => { form.restrictedModels.splice(index, 1) } +// 常用模型列表 +const commonModels = ref(['claude-opus-4-20250514', 'claude-opus-4-1-20250805']) + +// 可用的快捷模型(过滤掉已在限制列表中的) +const availableQuickModels = computed(() => { + return commonModels.value.filter((model) => !form.restrictedModels.includes(model)) +}) + +// 快速添加限制的模型 +const quickAddRestrictedModel = (model) => { + if (!form.restrictedModels.includes(model)) { + form.restrictedModels.push(model) + } +} + // 标签管理方法 const addTag = () => { if (newTag.value && newTag.value.trim()) { diff --git a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue index 64c04e39..2d3e98ce 100644 --- a/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue +++ b/web/admin-spa/src/components/apikeys/EditApiKeyModal.vue @@ -238,6 +238,27 @@

设置此 API Key 可同时处理的最大请求数

+ +
+
+ + +
+

+ 取消勾选将禁用此 API Key,暂停所有请求,客户端返回 401 错误 +

+
+
@@ -511,7 +532,8 @@ const form = reactive({ modelInput: '', enableClientRestriction: false, allowedClients: [], - tags: [] + tags: [], + isActive: true }) // 添加限制的模型 @@ -628,6 +650,9 @@ const updateApiKey = async () => { data.enableClientRestriction = form.enableClientRestriction data.allowedClients = form.allowedClients + // 活跃状态 + data.isActive = form.isActive + const result = await apiClient.put(`/admin/api-keys/${props.apiKey.id}`, data) if (result.success) { @@ -737,6 +762,8 @@ onMounted(async () => { // 从后端数据中获取实际的启用状态,而不是根据数组长度推断 form.enableModelRestriction = props.apiKey.enableModelRestriction || false form.enableClientRestriction = props.apiKey.enableClientRestriction || false + // 初始化活跃状态,默认为 true + form.isActive = props.apiKey.isActive !== undefined ? props.apiKey.isActive : true }) diff --git a/web/admin-spa/src/components/apikeys/WindowCountdown.vue b/web/admin-spa/src/components/apikeys/WindowCountdown.vue new file mode 100644 index 00000000..a94066c5 --- /dev/null +++ b/web/admin-spa/src/components/apikeys/WindowCountdown.vue @@ -0,0 +1,254 @@ + + + diff --git a/web/admin-spa/src/components/common/CustomDropdown.vue b/web/admin-spa/src/components/common/CustomDropdown.vue new file mode 100644 index 00000000..a67cc3b4 --- /dev/null +++ b/web/admin-spa/src/components/common/CustomDropdown.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/web/admin-spa/src/views/AccountsView.vue b/web/admin-spa/src/views/AccountsView.vue index fb53a73a..1234a53d 100644 --- a/web/admin-spa/src/views/AccountsView.vue +++ b/web/admin-spa/src/views/AccountsView.vue @@ -6,36 +6,65 @@

账户管理

管理您的 Claude 和 Gemini 账户及代理配置

-
-
- - +
+ + 刷新 +
+ +
@@ -710,6 +739,7 @@ import { apiClient } from '@/config/api' import { useConfirm } from '@/composables/useConfirm' import AccountForm from '@/components/accounts/AccountForm.vue' import ConfirmModal from '@/components/common/ConfirmModal.vue' +import CustomDropdown from '@/components/common/CustomDropdown.vue' // 使用确认弹窗 const { showConfirmModal, confirmOptions, showConfirm, handleConfirm, handleCancel } = useConfirm() @@ -726,6 +756,30 @@ const accountGroups = ref([]) const groupFilter = ref('all') const filteredAccounts = ref([]) +// 下拉选项数据 +const sortOptions = ref([ + { value: 'name', label: '按名称排序', icon: 'fa-font' }, + { value: 'dailyTokens', label: '按今日Token排序', icon: 'fa-coins' }, + { value: 'dailyRequests', label: '按今日请求数排序', icon: 'fa-chart-line' }, + { value: 'totalTokens', label: '按总Token排序', icon: 'fa-database' }, + { value: 'lastUsed', label: '按最后使用排序', icon: 'fa-clock' } +]) + +const groupOptions = computed(() => { + const options = [ + { value: 'all', label: '所有账户', icon: 'fa-globe' }, + { value: 'ungrouped', label: '未分组账户', icon: 'fa-user' } + ] + accountGroups.value.forEach((group) => { + options.push({ + value: group.id, + label: `${group.name} (${group.platform === 'claude' ? 'Claude' : 'Gemini'})`, + icon: group.platform === 'claude' ? 'fa-brain' : 'fa-robot' + }) + }) + return options +}) + // 模态框状态 const showCreateAccountModal = ref(false) const showEditAccountModal = ref(false) diff --git a/web/admin-spa/src/views/ApiKeysView.vue b/web/admin-spa/src/views/ApiKeysView.vue index f09dc163..2ad8cf54 100644 --- a/web/admin-spa/src/views/ApiKeysView.vue +++ b/web/admin-spa/src/views/ApiKeysView.vue @@ -7,35 +7,71 @@

管理和监控您的 API 密钥

- -
- + +
+ +
+
+ +
+ - +
+ + 刷新 +
+
@@ -368,6 +404,21 @@ + +