+
@@ -1152,6 +1194,11 @@ import CustomDropdown from '@/components/common/CustomDropdown.vue'
// 响应式数据
const clientsStore = useClientsStore()
const apiKeys = ref([])
+
+// 多选相关状态
+const selectedApiKeys = ref([])
+const selectAllChecked = ref(false)
+const isIndeterminate = ref(false)
const apiKeysLoading = ref(false)
const apiKeyStatsTimeRange = ref('today')
const apiKeysSortBy = ref('')
@@ -1831,6 +1878,12 @@ const deleteApiKey = async (keyId) => {
const data = await apiClient.delete(`/admin/api-keys/${keyId}`)
if (data.success) {
showToast('API Key 已删除', 'success')
+ // 从选中列表中移除
+ const index = selectedApiKeys.value.indexOf(keyId)
+ if (index > -1) {
+ selectedApiKeys.value.splice(index, 1)
+ }
+ updateSelectAllState()
loadApiKeys()
} else {
showToast(data.message || '删除失败', 'error')
@@ -1840,6 +1893,96 @@ const deleteApiKey = async (keyId) => {
}
}
+// 批量删除API Keys
+const batchDeleteApiKeys = async () => {
+ const selectedCount = selectedApiKeys.value.length
+ if (selectedCount === 0) {
+ showToast('请先选择要删除的 API Keys', 'warning')
+ return
+ }
+
+ let confirmed = false
+ const message = `确定要删除选中的 ${selectedCount} 个 API Key 吗?此操作不可恢复。`
+
+ if (window.showConfirm) {
+ confirmed = await window.showConfirm('批量删除 API Keys', message, '确定删除', '取消')
+ } else {
+ confirmed = confirm(message)
+ }
+
+ if (!confirmed) return
+
+ const keyIds = [...selectedApiKeys.value]
+
+ try {
+ const data = await apiClient.delete('/admin/api-keys/batch', {
+ data: { keyIds }
+ })
+
+ if (data.success) {
+ const { successCount, failedCount, errors } = data.data
+
+ if (successCount > 0) {
+ showToast(`成功删除 ${successCount} 个 API Keys`, 'success')
+
+ // 如果有失败的,显示详细信息
+ if (failedCount > 0) {
+ const errorMessages = errors.map((e) => `${e.keyId}: ${e.error}`).join('\n')
+ showToast(`${failedCount} 个删除失败:\n${errorMessages}`, 'warning')
+ }
+ } else {
+ showToast('所有 API Keys 删除失败', 'error')
+ }
+
+ // 清空选中状态
+ selectedApiKeys.value = []
+ updateSelectAllState()
+ loadApiKeys()
+ } else {
+ showToast(data.message || '批量删除失败', 'error')
+ }
+ } catch (error) {
+ showToast('批量删除失败', 'error')
+ console.error('批量删除 API Keys 失败:', error)
+ }
+}
+
+// 处理全选/取消全选
+const handleSelectAll = () => {
+ if (selectAllChecked.value) {
+ // 全选当前页的所有API Keys
+ paginatedApiKeys.value.forEach((key) => {
+ if (!selectedApiKeys.value.includes(key.id)) {
+ selectedApiKeys.value.push(key.id)
+ }
+ })
+ } else {
+ // 取消全选:只移除当前页的选中项,保留其他页面的选中项
+ const currentPageIds = new Set(paginatedApiKeys.value.map((key) => key.id))
+ selectedApiKeys.value = selectedApiKeys.value.filter((id) => !currentPageIds.has(id))
+ }
+ updateSelectAllState()
+}
+
+// 更新全选状态
+const updateSelectAllState = () => {
+ const totalInCurrentPage = paginatedApiKeys.value.length
+ const selectedInCurrentPage = paginatedApiKeys.value.filter((key) =>
+ selectedApiKeys.value.includes(key.id)
+ ).length
+
+ if (selectedInCurrentPage === 0) {
+ selectAllChecked.value = false
+ isIndeterminate.value = false
+ } else if (selectedInCurrentPage === totalInCurrentPage) {
+ selectAllChecked.value = true
+ isIndeterminate.value = false
+ } else {
+ selectAllChecked.value = false
+ isIndeterminate.value = true
+ }
+}
+
// 复制API统计页面链接
const copyApiStatsLink = (apiKey) => {
// 构建统计页面的完整URL
@@ -1980,14 +2123,43 @@ const clearSearch = () => {
currentPage.value = 1
}
-// 监听筛选条件变化,重置页码
-watch([selectedTagFilter, apiKeyStatsTimeRange, searchKeyword], () => {
+// 监听筛选条件变化,重置页码和选中状态
+// 监听筛选条件变化(不包括搜索),清空选中状态
+watch([selectedTagFilter, apiKeyStatsTimeRange], () => {
currentPage.value = 1
+ // 清空选中状态
+ selectedApiKeys.value = []
+ updateSelectAllState()
+})
+
+// 监听搜索关键词变化,只重置分页,保持选中状态
+watch(searchKeyword, () => {
+ currentPage.value = 1
+ // 不清空选中状态,允许跨搜索保持勾选
+ updateSelectAllState()
+})
+
+// 监听分页变化,更新全选状态
+watch([currentPage, pageSize], () => {
+ updateSelectAllState()
+})
+
+// 监听API Keys数据变化,清理无效的选中状态
+watch(apiKeys, () => {
+ const validIds = new Set(apiKeys.value.map((key) => key.id))
+
+ // 过滤出仍然有效的选中项
+ selectedApiKeys.value = selectedApiKeys.value.filter((id) => validIds.has(id))
+
+ updateSelectAllState()
})
onMounted(async () => {
// 并行加载所有需要的数据
await Promise.all([clientsStore.loadSupportedClients(), loadAccounts(), loadApiKeys()])
+
+ // 初始化全选状态
+ updateSelectAllState()
})