pre / static /js /api-key-manager /key-operations.js
yangtb24's picture
Upload 37 files
bbb6398 verified
raw
history blame
18 kB
/**
* 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;
}
}