/** * API密钥管理器 - 密钥操作模块 * 包含API密钥的加载、添加、删除、编辑等基本操作 */ // 加载API密钥 async function loadApiKeys() { this.isLoading = true; const startTime = Date.now(); try { // 通过AJAX获取完整的HTML部分而不仅仅是JSON数据 const response = await fetch('/?ajax=1'); const html = await response.text(); // 创建一个临时容器来解析HTML const tempContainer = document.createElement('div'); tempContainer.innerHTML = html; // 提取新的API密钥列表HTML const newKeyListHtml = tempContainer.querySelector('.space-y-6').outerHTML; // 替换当前页面上的API密钥列表 document.querySelector('.space-y-6').outerHTML = newKeyListHtml; // 重新初始化必要的事件监听器和组件 initScrollContainers(); // 同时更新本地数据 const jsonResponse = await fetch('/api/keys'); const data = await jsonResponse.json(); this.apiKeys = [...(data.api_keys || [])]; // 显式重置 selectedKeys 和 selectedPlatforms this.selectedKeys = []; this.selectedPlatforms = []; // 确保加载动画至少显示200毫秒,使体验更平滑 const elapsedTime = Date.now() - startTime; const minLoadTime = 200; // 最小加载时间(毫秒) if (elapsedTime < minLoadTime) { await new Promise(resolve => setTimeout(resolve, minLoadTime - elapsedTime)); } } catch (error) { console.error('加载API密钥失败:', error); Swal.fire({ icon: 'error', title: '加载失败', text: '无法加载API密钥,请刷新页面重试', confirmButtonColor: '#0284c7' }); } finally { this.isLoading = false; } } // 添加API密钥 async function addApiKey() { if (!this.newKey.platform || !this.newKey.key) { this.errorMessage = '请填写所有必填字段。'; return; } // 如果名称为空,生成自动名称 if (!this.newKey.name.trim()) { const date = new Date(); const dateStr = date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\//g, '-'); const timeStr = date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); this.newKey.name = `${dateStr} ${timeStr}`; } // 保存当前选择的平台类型 localStorage.setItem('lastSelectedPlatform', this.newKey.platform); this.isSubmitting = true; this.errorMessage = ''; try { // 处理输入文本:去除单引号、双引号、小括号、方括号、空格,然后分行 const lines = this.newKey.key .split('\n') .map(line => line.replace(/['"\(\)\[\]\s]/g, '')) // 去除单引号、双引号、小括号、方括号和空格 .filter(line => line.length > 0); // 过滤掉空行 // 从每一行中提取逗号分隔的非空元素,作为单独的key let keysWithDuplicates = []; for (const line of lines) { const lineKeys = line.split(',') .filter(item => item.length > 0); // 过滤掉空元素 // 将每个非空元素添加到数组 keysWithDuplicates.push(...lineKeys); } if (keysWithDuplicates.length === 0) { this.errorMessage = '请输入至少一个有效的API密钥。'; this.isSubmitting = false; return; } // 去除输入中重复的key(同一次提交中的重复) const inputDuplicatesCount = keysWithDuplicates.length - new Set(keysWithDuplicates).size; const keys = [...new Set(keysWithDuplicates)]; // 使用Set去重,得到唯一的keys数组 // 过滤掉已存在于同一平台的重复key const currentPlatform = this.newKey.platform; const existingKeys = this.apiKeys .filter(apiKey => apiKey.platform === currentPlatform) .map(apiKey => apiKey.key); const uniqueKeys = keys.filter(key => !existingKeys.includes(key)); const duplicateCount = keys.length - uniqueKeys.length; // 如果所有key都重复,显示错误消息并退出 if (uniqueKeys.length === 0) { this.errorMessage = '所有输入的API密钥在当前平台中已存在。'; this.isSubmitting = false; return; } // 批量添加API密钥(只添加不重复的key) const results = []; let allSuccess = true; // 记录重复和唯一的key数量,用于显示通知 const skippedCount = duplicateCount; const addedCount = uniqueKeys.length; for (const keyText of uniqueKeys) { const keyData = { platform: this.newKey.platform, name: this.newKey.name, key: keyText }; const response = await fetch('/api/keys', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(keyData), }); const data = await response.json(); results.push(data); if (!data.success) { allSuccess = false; } } if (allSuccess) { // 关闭模态框并重置表单 this.showAddModal = false; this.newKey = { platform: this.newKey.platform, // 保留平台选择 name: '', key: '' }; // 使用Toast风格的通知提示 const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2500, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); // 重新加载API密钥数据而不刷新页面 this.loadApiKeys(); // 构建通知消息 let title = `已添加 ${addedCount} 个API密钥`; // 根据不同情况显示通知 if (inputDuplicatesCount > 0 && skippedCount > 0) { // 既有输入中的重复,也有数据库中的重复 title += `,跳过 ${inputDuplicatesCount} 个输入重复和 ${skippedCount} 个已存在密钥`; } else if (inputDuplicatesCount > 0) { // 只有输入中的重复 title += `,跳过 ${inputDuplicatesCount} 个输入重复密钥`; } else if (skippedCount > 0) { // 只有数据库中的重复 title += `,跳过 ${skippedCount} 个已存在密钥`; } Toast.fire({ icon: 'success', title: title, background: '#f0fdf4', iconColor: '#16a34a' }); } else { // 部分失败或全部失败 const successCount = results.filter(r => r.success).length; const failCount = results.length - successCount; this.errorMessage = `添加操作部分失败: 成功 ${successCount} 个, 失败 ${failCount} 个`; } } catch (error) { console.error('添加API密钥失败:', error); this.errorMessage = '服务器错误,请重试。'; } finally { this.isSubmitting = false; } } // 删除API密钥 function deleteApiKey(id, name) { this.deleteKeyId = id; this.deleteKeyName = name; this.showDeleteConfirm = true; } // 确认删除(单个或批量) async function confirmDelete() { if (this.isBulkDelete) { if (this.selectedKeys.length === 0) return; this.isDeleting = true; try { const response = await fetch('/api/keys/bulk-delete', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ ids: this.selectedKeys }), }); const data = await response.json(); if (data.success) { // 关闭模态框,清空选中数组 this.showDeleteConfirm = false; this.isBulkDelete = false; const deletedCount = data.deleted_count || this.selectedKeys.length; // 清空选中数组 this.selectedKeys = []; this.selectedPlatforms = []; // 使用Toast风格的通知提示 const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 1500, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); // 重新加载API密钥数据而不刷新页面 this.loadApiKeys(); Toast.fire({ icon: 'success', title: `成功删除 ${deletedCount} 个API密钥`, background: '#fee2e2', iconColor: '#ef4444' }); } else { Swal.fire({ icon: 'error', title: '批量删除失败', text: data.error || '删除操作未能完成,请重试', confirmButtonColor: '#0284c7' }); } } catch (error) { console.error('批量删除API密钥失败:', error); Swal.fire({ icon: 'error', title: '服务器错误', text: '无法完成删除操作,请稍后重试', confirmButtonColor: '#0284c7' }); } finally { this.isDeleting = false; } } else { // 单个删除逻辑 if (!this.deleteKeyId) return; this.isDeleting = true; try { const response = await fetch(`/api/keys/${this.deleteKeyId}`, { method: 'DELETE', }); const data = await response.json(); if (data.success) { // 从本地数组中移除 (创建新数组) this.apiKeys = [...this.apiKeys.filter(key => key.id !== this.deleteKeyId)]; // 关闭模态框 this.showDeleteConfirm = false; // 使用Toast风格的通知提示 const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 1500, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); // 重新加载API密钥数据而不刷新页面 this.loadApiKeys(); Toast.fire({ icon: 'success', title: 'API密钥已删除', background: '#fee2e2', iconColor: '#ef4444' }); } else { Swal.fire({ icon: 'error', title: '删除失败', text: data.message || '删除操作未能完成,请重试', confirmButtonColor: '#0284c7' }); } } catch (error) { console.error('删除API密钥失败:', error); Swal.fire({ icon: 'error', title: '服务器错误', text: '无法完成删除操作,请稍后重试', confirmButtonColor: '#0284c7' }); } finally { this.isDeleting = false; } } } // 打开编辑API密钥模态框 function editApiKey(id, name, key, platform) { // 如果platform参数不存在,尝试从apiKeys中查找 if (!platform) { const apiKey = this.apiKeys.find(key => key.id === id); if (apiKey) { platform = apiKey.platform; } } this.editKey = { id: id, name: name, key: key, platform: platform }; this.showEditModal = true; this.errorMessage = ''; // 聚焦到名称输入框 setTimeout(() => { document.getElementById('edit-name').focus(); }, 100); } // 更新API密钥 async function updateApiKey() { if (!this.editKey.key) { this.errorMessage = '请填写API密钥值。'; return; } this.isSubmitting = true; this.errorMessage = ''; try { // 检查修改后的key是否与同一平台下的其他key重复 const currentPlatform = this.editKey.platform; const currentId = this.editKey.id; const editedKey = this.editKey.key.trim(); // 获取同平台下除当前key外的所有key const duplicateKey = this.apiKeys.find(apiKey => apiKey.platform === currentPlatform && apiKey.id !== currentId && apiKey.key === editedKey ); // 如果发现重复key,则自动删除当前key if (duplicateKey) { // 删除当前key const deleteResponse = await fetch(`/api/keys/${currentId}`, { method: 'DELETE', }); const deleteData = await deleteResponse.json(); if (deleteData.success) { // 关闭模态框 this.showEditModal = false; // 使用Toast风格的通知提示 const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 2500, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); // 重新加载API密钥数据而不刷新页面 this.loadApiKeys(); Toast.fire({ icon: 'info', title: '发现重复密钥,已自动删除', background: '#e0f2fe', iconColor: '#0284c7' }); return; } else { this.errorMessage = '发现重复密钥,但自动删除失败,请手动处理。'; this.isSubmitting = false; return; } } // 如果没有重复,正常更新 const response = await fetch(`/api/keys/${this.editKey.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: this.editKey.name, key: editedKey }), }); const data = await response.json(); if (data.success) { // 关闭模态框 this.showEditModal = false; // 使用Toast风格的通知提示 const Toast = Swal.mixin({ toast: true, position: 'top-end', showConfirmButton: false, timer: 1500, timerProgressBar: true, didOpen: (toast) => { toast.onmouseenter = Swal.stopTimer; toast.onmouseleave = Swal.resumeTimer; } }); // 重新加载API密钥数据而不刷新页面 this.loadApiKeys(); Toast.fire({ icon: 'success', title: 'API密钥已更新', background: '#f0fdf4', iconColor: '#16a34a' }); } else { this.errorMessage = data.error || '更新失败,请重试。'; } } catch (error) { console.error('更新API密钥失败:', error); this.errorMessage = '服务器错误,请重试。'; } finally { this.isSubmitting = false; } }