mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-14 20:04:46 +08:00
feat(account): 优化批量更新实现,使用统一 SQL 合并 JSONB 字段
- 新增 BulkUpdate 仓储方法,使用单条 SQL 更新所有账户 - credentials/extra 使用 COALESCE(...) || ? 合并,只更新传入的 key - name/proxy_id/concurrency/priority/status 只在提供时更新 - 分组绑定仍逐账号处理(需要独立操作) - 前端优化:Base URL 留空则不修改,按勾选字段更新 - 完善 i18n 文案:说明留空不修改、批量更新行为
This commit is contained in:
@@ -255,7 +255,7 @@ export async function bulkUpdate(
|
||||
results: Array<{ account_id: number; success: boolean; error?: string }>;
|
||||
}>('/admin/accounts/bulk-update', {
|
||||
account_ids: accountIds,
|
||||
updates
|
||||
...updates
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
@@ -443,7 +443,7 @@ import { ref, watch, computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { adminAPI } from '@/api/admin'
|
||||
import type { Proxy, Group, Account } from '@/types'
|
||||
import type { Proxy, Group } from '@/types'
|
||||
import Modal from '@/components/common/Modal.vue'
|
||||
import Select from '@/components/common/Select.vue'
|
||||
import ProxySelector from '@/components/common/ProxySelector.vue'
|
||||
@@ -496,7 +496,6 @@ const concurrency = ref(1)
|
||||
const priority = ref(1)
|
||||
const status = ref<'active' | 'inactive'>('active')
|
||||
const groupIds = ref<number[]>([])
|
||||
const accountCache = ref<Record<number, Account>>({})
|
||||
|
||||
// All models list (combined Anthropic + OpenAI)
|
||||
const allModels = [
|
||||
@@ -613,22 +612,10 @@ const buildModelMappingObject = (): Record<string, string> | null => {
|
||||
return Object.keys(mapping).length > 0 ? mapping : null
|
||||
}
|
||||
|
||||
const getDefaultBaseUrl = (platform: string) => {
|
||||
return platform === 'openai' ? 'https://api.openai.com' : 'https://api.anthropic.com'
|
||||
}
|
||||
|
||||
const getAccountDetails = async (accountId: number): Promise<Account> => {
|
||||
if (accountCache.value[accountId]) return accountCache.value[accountId]
|
||||
const account = await adminAPI.accounts.getById(accountId)
|
||||
accountCache.value[accountId] = account
|
||||
return account
|
||||
}
|
||||
|
||||
const buildUpdatePayload = (account: Account): Record<string, unknown> | null => {
|
||||
const buildUpdatePayload = (): Record<string, unknown> | null => {
|
||||
const updates: Record<string, unknown> = {}
|
||||
let credentials: Record<string, unknown> | null = null
|
||||
const credentials: Record<string, unknown> = {}
|
||||
let credentialsChanged = false
|
||||
const isAnthropic = account.platform === 'anthropic'
|
||||
|
||||
if (enableProxy.value) {
|
||||
updates.proxy_id = proxyId.value
|
||||
@@ -650,47 +637,34 @@ const buildUpdatePayload = (account: Account): Record<string, unknown> | null =>
|
||||
updates.group_ids = groupIds.value
|
||||
}
|
||||
|
||||
if (account.type === 'apikey') {
|
||||
const baseCredentials = (account.credentials || {}) as Record<string, unknown>
|
||||
credentials = { ...baseCredentials }
|
||||
|
||||
if (enableBaseUrl.value) {
|
||||
credentials.base_url = baseUrl.value.trim() || getDefaultBaseUrl(account.platform)
|
||||
if (enableBaseUrl.value) {
|
||||
const baseUrlValue = baseUrl.value.trim()
|
||||
if (baseUrlValue) {
|
||||
credentials.base_url = baseUrlValue
|
||||
credentialsChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if (enableModelRestriction.value) {
|
||||
const modelMapping = buildModelMappingObject()
|
||||
if (modelMapping) {
|
||||
credentials.model_mapping = modelMapping
|
||||
} else {
|
||||
delete credentials.model_mapping
|
||||
}
|
||||
if (enableModelRestriction.value) {
|
||||
const modelMapping = buildModelMappingObject()
|
||||
if (modelMapping) {
|
||||
credentials.model_mapping = modelMapping
|
||||
credentialsChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
if (enableCustomErrorCodes.value) {
|
||||
credentials.custom_error_codes_enabled = true
|
||||
credentials.custom_error_codes = [...selectedErrorCodes.value]
|
||||
credentialsChanged = true
|
||||
}
|
||||
|
||||
if (enableInterceptWarmup.value && isAnthropic) {
|
||||
credentials.intercept_warmup_requests = interceptWarmupRequests.value
|
||||
credentialsChanged = true
|
||||
}
|
||||
} else if (enableInterceptWarmup.value && isAnthropic) {
|
||||
const baseCredentials = (account.credentials || {}) as Record<string, unknown>
|
||||
credentials = { ...baseCredentials }
|
||||
if (interceptWarmupRequests.value) {
|
||||
credentials.intercept_warmup_requests = true
|
||||
} else {
|
||||
delete credentials.intercept_warmup_requests
|
||||
}
|
||||
if (enableCustomErrorCodes.value) {
|
||||
credentials.custom_error_codes_enabled = true
|
||||
credentials.custom_error_codes = [...selectedErrorCodes.value]
|
||||
credentialsChanged = true
|
||||
}
|
||||
|
||||
if (credentials && credentialsChanged) {
|
||||
if (enableInterceptWarmup.value) {
|
||||
credentials.intercept_warmup_requests = interceptWarmupRequests.value
|
||||
credentialsChanged = true
|
||||
}
|
||||
|
||||
if (credentialsChanged) {
|
||||
updates.credentials = credentials
|
||||
}
|
||||
|
||||
@@ -722,39 +696,37 @@ const handleSubmit = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
const updates = buildUpdatePayload()
|
||||
if (!updates) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.noFieldsSelected'))
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
let success = 0
|
||||
let failed = 0
|
||||
|
||||
for (const accountId of props.accountIds) {
|
||||
try {
|
||||
const account = await getAccountDetails(accountId)
|
||||
const updates = buildUpdatePayload(account)
|
||||
if (!updates) {
|
||||
continue
|
||||
}
|
||||
await adminAPI.accounts.update(accountId, updates)
|
||||
success++
|
||||
} catch (error: any) {
|
||||
failed++
|
||||
console.error(`Error bulk updating account ${accountId}:`, error)
|
||||
try {
|
||||
const res = await adminAPI.accounts.bulkUpdate(props.accountIds, updates)
|
||||
const success = res.success || 0
|
||||
const failed = res.failed || 0
|
||||
|
||||
if (success > 0 && failed === 0) {
|
||||
appStore.showSuccess(t('admin.accounts.bulkEdit.success', { count: success }))
|
||||
} else if (success > 0) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.partialSuccess', { success, failed }))
|
||||
} else {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.failed'))
|
||||
}
|
||||
}
|
||||
|
||||
if (success > 0 && failed === 0) {
|
||||
appStore.showSuccess(t('admin.accounts.bulkEdit.success', { count: success }))
|
||||
} else if (success > 0) {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.partialSuccess', { success, failed }))
|
||||
} else {
|
||||
appStore.showError(t('admin.accounts.bulkEdit.failed'))
|
||||
if (success > 0) {
|
||||
emit('updated')
|
||||
handleClose()
|
||||
}
|
||||
} catch (error: any) {
|
||||
appStore.showError(error.response?.data?.detail || t('admin.accounts.bulkEdit.failed'))
|
||||
console.error('Error bulk updating accounts:', error)
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
if (success > 0) {
|
||||
emit('updated')
|
||||
handleClose()
|
||||
}
|
||||
|
||||
submitting.value = false
|
||||
}
|
||||
|
||||
// Reset form when modal closes
|
||||
@@ -784,7 +756,6 @@ watch(() => props.show, (newShow) => {
|
||||
priority.value = 1
|
||||
status.value = 'active'
|
||||
groupIds.value = []
|
||||
accountCache.value = {}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -756,7 +756,7 @@ export default {
|
||||
title: 'Bulk Edit Accounts',
|
||||
selectionInfo: '{count} account(s) selected. Only checked or filled fields will be updated; others stay unchanged.',
|
||||
baseUrlPlaceholder: 'https://api.anthropic.com or https://api.openai.com',
|
||||
baseUrlNotice: 'Applies to API Key accounts only; leave empty to use the platform default',
|
||||
baseUrlNotice: 'Applies to API Key accounts only; leave empty to keep existing value',
|
||||
submit: 'Update Accounts',
|
||||
updating: 'Updating...',
|
||||
success: 'Updated {count} account(s)',
|
||||
|
||||
@@ -887,7 +887,7 @@ export default {
|
||||
title: '批量编辑账号',
|
||||
selectionInfo: '已选择 {count} 个账号。只更新您勾选或填写的字段,未勾选的字段保持不变。',
|
||||
baseUrlPlaceholder: 'https://api.anthropic.com 或 https://api.openai.com',
|
||||
baseUrlNotice: '仅适用于 API Key 账号,留空使用对应平台默认地址',
|
||||
baseUrlNotice: '仅适用于 API Key 账号,留空则不修改',
|
||||
submit: '批量更新',
|
||||
updating: '更新中...',
|
||||
success: '成功更新 {count} 个账号',
|
||||
|
||||
Reference in New Issue
Block a user