diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue
index 0bea28a8..da6a169e 100644
--- a/web/admin-spa/src/components/accounts/AccountForm.vue
+++ b/web/admin-spa/src/components/accounts/AccountForm.vue
@@ -4841,11 +4841,23 @@ const handleGroupRefresh = async () => {
// 处理 API Key 管理模态框刷新
const handleApiKeyRefresh = async () => {
// 刷新账户信息以更新 API Key 数量
- if (props.account?.id) {
+ if (!props.account?.id) {
+ return
+ }
+
+ const refreshers = [
+ typeof accountsStore.fetchDroidAccounts === 'function'
+ ? accountsStore.fetchDroidAccounts
+ : null,
+ typeof accountsStore.fetchAllAccounts === 'function' ? accountsStore.fetchAllAccounts : null
+ ].filter(Boolean)
+
+ for (const refresher of refreshers) {
try {
- await accountsStore.fetchAccounts()
+ await refresher()
+ return
} catch (error) {
- console.error('Failed to refresh account data:', error)
+ console.error('刷新账户列表失败:', error)
}
}
}
diff --git a/web/admin-spa/src/components/accounts/ApiKeyManagementModal.vue b/web/admin-spa/src/components/accounts/ApiKeyManagementModal.vue
index ad062150..dc6f95c3 100644
--- a/web/admin-spa/src/components/accounts/ApiKeyManagementModal.vue
+++ b/web/admin-spa/src/components/accounts/ApiKeyManagementModal.vue
@@ -20,12 +20,28 @@
-
+
+
+
+
@@ -239,6 +255,7 @@ const resetting = ref(null)
const apiKeys = ref([])
const currentPage = ref(1)
const pageSize = ref(18)
+const copyingAll = ref(false)
// 计算属性
const totalItems = computed(() => apiKeys.value.length)
@@ -403,14 +420,71 @@ const maskApiKey = (key) => {
return `${key.substring(0, 8)}...${key.substring(key.length - 4)}`
}
+// 写入剪贴板(带回退逻辑)
+const writeToClipboard = async (text) => {
+ const canUseClipboardApi =
+ typeof navigator !== 'undefined' &&
+ navigator.clipboard &&
+ typeof navigator.clipboard.writeText === 'function' &&
+ (typeof window === 'undefined' || window.isSecureContext !== false)
+
+ if (canUseClipboardApi) {
+ await navigator.clipboard.writeText(text)
+ return
+ }
+
+ if (typeof document === 'undefined') {
+ throw new Error('clipboard unavailable')
+ }
+
+ const textarea = document.createElement('textarea')
+ textarea.value = text
+ textarea.setAttribute('readonly', '')
+ textarea.style.position = 'fixed'
+ textarea.style.opacity = '0'
+ textarea.style.pointerEvents = 'none'
+ document.body.appendChild(textarea)
+ textarea.select()
+
+ try {
+ const success = document.execCommand('copy')
+ document.body.removeChild(textarea)
+ if (!success) {
+ throw new Error('execCommand failed')
+ }
+ } catch (error) {
+ document.body.removeChild(textarea)
+ throw error
+ }
+}
+
// 复制 API Key
const copyApiKey = async (key) => {
try {
- await navigator.clipboard.writeText(key)
+ await writeToClipboard(key)
showToast('API Key 已复制', 'success')
} catch (error) {
console.error('Failed to copy:', error)
- showToast('复制失败', 'error')
+ showToast('复制失败,请手动复制', 'error')
+ }
+}
+
+// 复制全部 API Key
+const copyAllApiKeys = async () => {
+ if (!apiKeys.value.length || copyingAll.value) {
+ return
+ }
+
+ copyingAll.value = true
+ try {
+ const allKeysText = apiKeys.value.map((item) => item.key).join('\n')
+ await writeToClipboard(allKeysText)
+ showToast(`已复制 ${apiKeys.value.length} 条 API Key`, 'success')
+ } catch (error) {
+ console.error('Failed to copy all keys:', error)
+ showToast('复制全部 API Key 失败,请手动复制', 'error')
+ } finally {
+ copyingAll.value = false
}
}