@@ -238,21 +392,66 @@ const deleting = ref(null)
const resetting = ref(null)
const apiKeys = ref([])
const currentPage = ref(1)
-const pageSize = ref(18)
+const pageSize = ref(15)
+
+// 新增:筛选和搜索相关状态
+const statusFilter = ref('all') // 'all' | 'active' | 'error'
+const searchQuery = ref('')
+const searchMode = ref('fuzzy') // 'fuzzy' | 'exact'
+const batchDeleting = ref(false)
+
+// 掩码显示 API Key(提前声明供 computed 使用)
+const maskApiKey = (key) => {
+ if (!key || key.length < 12) {
+ return key
+ }
+ return `${key.substring(0, 8)}...${key.substring(key.length - 4)}`
+}
+
+// 计算属性:筛选后的 API Keys
+const filteredApiKeys = computed(() => {
+ let filtered = apiKeys.value
+
+ // 状态筛选
+ if (statusFilter.value !== 'all') {
+ filtered = filtered.filter((key) => key.status === statusFilter.value)
+ }
+
+ // 搜索筛选(使用完整的 key 进行搜索)
+ if (searchQuery.value.trim()) {
+ const query = searchQuery.value.trim()
+ filtered = filtered.filter((key) => {
+ const fullKey = key.key // 使用完整的 key
+ if (searchMode.value === 'exact') {
+ // 精确搜索:完全匹配完整的 key
+ return fullKey === query
+ } else {
+ // 模糊搜索:完整 key 包含查询字符串(不区分大小写)
+ return fullKey.toLowerCase().includes(query.toLowerCase())
+ }
+ })
+ }
+
+ return filtered
+})
// 计算属性
-const totalItems = computed(() => apiKeys.value.length)
+const totalItems = computed(() => filteredApiKeys.value.length)
const totalPages = computed(() => Math.ceil(totalItems.value / pageSize.value))
const paginatedApiKeys = computed(() => {
const start = (currentPage.value - 1) * pageSize.value
const end = start + pageSize.value
- return apiKeys.value.slice(start, end)
+ return filteredApiKeys.value.slice(start, end)
})
-// 获取原始索引的方法
-const getOriginalIndex = (paginatedIndex) => {
- return (currentPage.value - 1) * pageSize.value + paginatedIndex
-}
+// 统计数量
+const activeKeysCount = computed(() => {
+ return apiKeys.value.filter((key) => key.status === 'active').length
+})
+
+const errorKeysCount = computed(() => {
+ return apiKeys.value.filter((key) => key.status === 'error').length
+})
// 加载 API Keys
const loadApiKeys = async () => {
@@ -332,12 +531,12 @@ const loadApiKeys = async () => {
}
// 删除 API Key
-const deleteApiKey = async (apiKey, index) => {
+const deleteApiKey = async (apiKey) => {
if (!confirm(`确定要删除 API Key "${maskApiKey(apiKey.key)}" 吗?`)) {
return
}
- deleting.value = index
+ deleting.value = apiKey.key
try {
// 准备更新数据:删除指定的 key
const updateData = {
@@ -359,7 +558,7 @@ const deleteApiKey = async (apiKey, index) => {
}
// 重置 API Key 状态
-const resetApiKeyStatus = async (apiKey, index) => {
+const resetApiKeyStatus = async (apiKey) => {
if (
!confirm(
`确定要重置 API Key "${maskApiKey(apiKey.key)}" 的状态吗?这将清除错误信息并恢复为正常状态。`
@@ -368,7 +567,7 @@ const resetApiKeyStatus = async (apiKey, index) => {
return
}
- resetting.value = index
+ resetting.value = apiKey.key
try {
// 准备更新数据:重置指定 key 的状态
const updateData = {
@@ -395,12 +594,113 @@ const resetApiKeyStatus = async (apiKey, index) => {
}
}
-// 掩码显示 API Key
-const maskApiKey = (key) => {
- if (!key || key.length < 12) {
- return key
+// 批量删除所有异常状态的 Key
+const deleteAllErrorKeys = async () => {
+ const errorKeys = apiKeys.value.filter((key) => key.status === 'error')
+ if (errorKeys.length === 0) {
+ showToast('没有异常状态的 API Key', 'warning')
+ return
}
- return `${key.substring(0, 8)}...${key.substring(key.length - 4)}`
+
+ if (!confirm(`确定要删除所有 ${errorKeys.length} 个异常状态的 API Key 吗?此操作不可恢复!`)) {
+ return
+ }
+
+ batchDeleting.value = true
+ try {
+ const keysToDelete = errorKeys.map((key) => key.key)
+ const updateData = {
+ removeApiKeys: keysToDelete,
+ apiKeyUpdateMode: 'delete'
+ }
+
+ await apiClient.put(`/admin/droid-accounts/${props.accountId}`, updateData)
+
+ showToast(`成功删除 ${errorKeys.length} 个异常 API Key`, 'success')
+ await loadApiKeys()
+ emit('refresh')
+ } catch (error) {
+ console.error('Failed to delete error API keys:', error)
+ showToast(error.response?.data?.error || '批量删除失败', 'error')
+ } finally {
+ batchDeleting.value = false
+ }
+}
+
+// 批量删除所有 Key
+const deleteAllKeys = async () => {
+ if (apiKeys.value.length === 0) {
+ showToast('没有可删除的 API Key', 'warning')
+ return
+ }
+
+ if (
+ !confirm(
+ `确定要删除所有 ${apiKeys.value.length} 个 API Key 吗?此操作不可恢复!\n\n请再次确认:这将删除该账户下的所有 API Key。`
+ )
+ ) {
+ return
+ }
+
+ // 二次确认
+ if (!confirm('最后确认:真的要删除所有 API Key 吗?')) {
+ return
+ }
+
+ batchDeleting.value = true
+ try {
+ const keysToDelete = apiKeys.value.map((key) => key.key)
+ const updateData = {
+ removeApiKeys: keysToDelete,
+ apiKeyUpdateMode: 'delete'
+ }
+
+ await apiClient.put(`/admin/droid-accounts/${props.accountId}`, updateData)
+
+ showToast(`成功删除所有 ${keysToDelete.length} 个 API Key`, 'success')
+ await loadApiKeys()
+ emit('refresh')
+ } catch (error) {
+ console.error('Failed to delete all API keys:', error)
+ showToast(error.response?.data?.error || '批量删除失败', 'error')
+ } finally {
+ batchDeleting.value = false
+ }
+}
+
+// 导出 Key
+const exportKeys = (type) => {
+ let keysToExport = []
+ let filename = ''
+
+ if (type === 'error') {
+ keysToExport = apiKeys.value.filter((key) => key.status === 'error')
+ filename = `error_api_keys_${props.accountName}_${new Date().toISOString().split('T')[0]}.txt`
+ } else {
+ keysToExport = apiKeys.value
+ filename = `all_api_keys_${props.accountName}_${new Date().toISOString().split('T')[0]}.txt`
+ }
+
+ if (keysToExport.length === 0) {
+ showToast('没有可导出的 API Key', 'warning')
+ return
+ }
+
+ // 生成 TXT 内容(每行一个完整的 key)
+ const content = keysToExport.map((key) => key.key).join('\n')
+
+ // 创建下载
+ const blob = new Blob([content], { type: 'text/plain;charset=utf-8' })
+ const url = URL.createObjectURL(blob)
+ const link = document.createElement('a')
+ link.href = url
+ link.download = filename
+ document.body.appendChild(link)
+ link.click()
+ document.body.removeChild(link)
+ URL.revokeObjectURL(url)
+
+ showToast(`成功导出 ${keysToExport.length} 个 API Key`, 'success')
}
// 复制 API Key