mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 新增标准Claude Console API账号支持
This commit is contained in:
4
web/admin-spa/dist/index.html
vendored
4
web/admin-spa/dist/index.html
vendored
@@ -18,12 +18,12 @@
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://cdn.jsdelivr.net">
|
||||
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
|
||||
<script type="module" crossorigin src="/admin-next/assets/index-DhITICXu.js"></script>
|
||||
<script type="module" crossorigin src="/admin-next/assets/index-yITK4-m_.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/vue-vendor-CKToUHZx.js">
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/vendor-BDiMbLwQ.js">
|
||||
<link rel="modulepreload" crossorigin href="/admin-next/assets/element-plus-B8Fs_0jW.js">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/element-plus-CPnoEkWW.css">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/index-Bv1yS6pY.css">
|
||||
<link rel="stylesheet" crossorigin href="/admin-next/assets/index-DX8B4Y8f.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -53,6 +53,15 @@
|
||||
>
|
||||
<span class="text-sm text-gray-700">Claude</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
v-model="form.platform"
|
||||
value="claude-console"
|
||||
class="mr-2"
|
||||
>
|
||||
<span class="text-sm text-gray-700">Claude Console</span>
|
||||
</label>
|
||||
<label class="flex items-center cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
@@ -65,7 +74,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!isEdit">
|
||||
<div v-if="!isEdit && form.platform !== 'claude-console'">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">添加方式</label>
|
||||
<div class="flex gap-4">
|
||||
<label class="flex items-center cursor-pointer">
|
||||
@@ -168,8 +177,107 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Claude Console 特定字段 -->
|
||||
<div v-if="form.platform === 'claude-console' && !isEdit" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">API URL *</label>
|
||||
<input
|
||||
v-model="form.apiUrl"
|
||||
type="text"
|
||||
required
|
||||
class="form-input w-full"
|
||||
:class="{ 'border-red-500': errors.apiUrl }"
|
||||
placeholder="例如:https://api.example.com"
|
||||
>
|
||||
<p v-if="errors.apiUrl" class="text-red-500 text-xs mt-1">{{ errors.apiUrl }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">API Key *</label>
|
||||
<input
|
||||
v-model="form.apiKey"
|
||||
type="password"
|
||||
required
|
||||
class="form-input w-full"
|
||||
:class="{ 'border-red-500': errors.apiKey }"
|
||||
placeholder="请输入API Key"
|
||||
>
|
||||
<p v-if="errors.apiKey" class="text-red-500 text-xs mt-1">{{ errors.apiKey }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">支持的模型 (可选)</label>
|
||||
<div class="mb-2 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="addPresetModel('claude-sonnet-4-20250514')"
|
||||
class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-colors"
|
||||
>
|
||||
+ claude-sonnet-4-20250514
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="addPresetModel('claude-opus-4-20250514')"
|
||||
class="px-3 py-1 text-xs bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors"
|
||||
>
|
||||
+ claude-opus-4-20250514
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="addPresetModel('claude-3-5-haiku-20241022')"
|
||||
class="px-3 py-1 text-xs bg-green-100 text-green-700 rounded-lg hover:bg-purple-200 transition-colors"
|
||||
>
|
||||
+ claude-3-5-haiku-20241022
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="form.supportedModels"
|
||||
rows="3"
|
||||
class="form-input w-full resize-none"
|
||||
placeholder="每行一个模型,例如: claude-3-opus-20240229 claude-3-sonnet-20240229 留空表示支持所有模型"
|
||||
></textarea>
|
||||
<p class="text-xs text-gray-500 mt-1">留空表示支持所有模型。如果指定模型,请求中的模型不在列表内将不会调度到此账号</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">自定义 User-Agent (可选)</label>
|
||||
<input
|
||||
v-model="form.userAgent"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="默认:claude-cli/1.0.61 (console, cli)"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">限流时间 (分钟)</label>
|
||||
<input
|
||||
v-model.number="form.rateLimitDuration"
|
||||
type="number"
|
||||
min="1"
|
||||
class="form-input w-full"
|
||||
placeholder="默认60分钟"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">当账号返回429错误时,暂停调度的时间(分钟)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Claude和Claude Console的优先级设置 -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console')">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">调度优先级 (1-100)</label>
|
||||
<input
|
||||
v-model.number="form.priority"
|
||||
type="number"
|
||||
min="1"
|
||||
max="100"
|
||||
class="form-input w-full"
|
||||
placeholder="数字越小优先级越高,默认50"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">数字越小优先级越高,建议范围:1-100</p>
|
||||
</div>
|
||||
|
||||
<!-- 手动输入 Token 字段 -->
|
||||
<div v-if="form.addType === 'manual'" class="space-y-4 bg-blue-50 p-4 rounded-lg border border-blue-200">
|
||||
<div v-if="form.addType === 'manual' && form.platform !== 'claude-console'" class="space-y-4 bg-blue-50 p-4 rounded-lg border border-blue-200">
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fas fa-info text-white text-sm"></i>
|
||||
@@ -235,7 +343,7 @@
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
v-if="form.addType === 'oauth'"
|
||||
v-if="form.addType === 'oauth' && form.platform !== 'claude-console'"
|
||||
type="button"
|
||||
@click="nextStep"
|
||||
:disabled="loading"
|
||||
@@ -331,8 +439,93 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Claude和Claude Console的优先级设置(编辑模式) -->
|
||||
<div v-if="(form.platform === 'claude' || form.platform === 'claude-console')">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">调度优先级 (1-100)</label>
|
||||
<input
|
||||
v-model.number="form.priority"
|
||||
type="number"
|
||||
min="1"
|
||||
max="100"
|
||||
class="form-input w-full"
|
||||
placeholder="数字越小优先级越高"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">数字越小优先级越高,建议范围:1-100</p>
|
||||
</div>
|
||||
|
||||
<!-- Claude Console 特定字段(编辑模式)-->
|
||||
<div v-if="form.platform === 'claude-console'" class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">API URL</label>
|
||||
<input
|
||||
v-model="form.apiUrl"
|
||||
type="text"
|
||||
required
|
||||
class="form-input w-full"
|
||||
placeholder="例如:https://api.example.com"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">API Key</label>
|
||||
<input
|
||||
v-model="form.apiKey"
|
||||
type="password"
|
||||
class="form-input w-full"
|
||||
placeholder="留空表示不更新"
|
||||
>
|
||||
<p class="text-xs text-gray-500 mt-1">留空表示不更新 API Key</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">支持的模型 (可选)</label>
|
||||
<div class="mb-2 flex gap-2">
|
||||
<button
|
||||
type="button"
|
||||
@click="addPresetModel('claude-sonnet-4-20250514')"
|
||||
class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-colors"
|
||||
>
|
||||
+ claude-sonnet-4-20250514
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="addPresetModel('claude-opus-4-20250514')"
|
||||
class="px-3 py-1 text-xs bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors"
|
||||
>
|
||||
+ claude-opus-4-20250514
|
||||
</button>
|
||||
</div>
|
||||
<textarea
|
||||
v-model="form.supportedModels"
|
||||
rows="3"
|
||||
class="form-input w-full resize-none"
|
||||
placeholder="每行一个模型,留空表示支持所有模型"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">自定义 User-Agent (可选)</label>
|
||||
<input
|
||||
v-model="form.userAgent"
|
||||
type="text"
|
||||
class="form-input w-full"
|
||||
placeholder="默认:claude-cli/1.0.61 (console, cli)"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">限流时间 (分钟)</label>
|
||||
<input
|
||||
v-model.number="form.rateLimitDuration"
|
||||
type="number"
|
||||
min="1"
|
||||
class="form-input w-full"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Token 更新 -->
|
||||
<div class="bg-amber-50 p-4 rounded-lg border border-amber-200">
|
||||
<div v-if="form.platform !== 'claude-console'" class="bg-amber-50 p-4 rounded-lg border border-amber-200">
|
||||
<div class="flex items-start gap-3 mb-4">
|
||||
<div class="w-8 h-8 bg-amber-500 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
|
||||
<i class="fas fa-key text-white text-sm"></i>
|
||||
@@ -466,13 +659,22 @@ const form = ref({
|
||||
projectId: props.account?.projectId || '',
|
||||
accessToken: '',
|
||||
refreshToken: '',
|
||||
proxy: initProxyConfig()
|
||||
proxy: initProxyConfig(),
|
||||
// Claude Console 特定字段
|
||||
apiUrl: props.account?.apiUrl || '',
|
||||
apiKey: props.account?.apiKey || '',
|
||||
priority: props.account?.priority || 50,
|
||||
supportedModels: props.account?.supportedModels?.join('\n') || '',
|
||||
userAgent: props.account?.userAgent || '',
|
||||
rateLimitDuration: props.account?.rateLimitDuration || 60
|
||||
})
|
||||
|
||||
// 表单验证错误
|
||||
const errors = ref({
|
||||
name: '',
|
||||
accessToken: ''
|
||||
accessToken: '',
|
||||
apiUrl: '',
|
||||
apiKey: ''
|
||||
})
|
||||
|
||||
// 计算是否可以进入下一步
|
||||
@@ -539,6 +741,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
if (form.value.platform === 'claude') {
|
||||
// Claude使用claudeAiOauth字段
|
||||
data.claudeAiOauth = tokenInfo.claudeAiOauth || tokenInfo
|
||||
data.priority = form.value.priority || 50
|
||||
} else if (form.value.platform === 'gemini') {
|
||||
// Gemini使用geminiOauth字段
|
||||
data.geminiOauth = tokenInfo.tokens || tokenInfo
|
||||
@@ -567,6 +770,8 @@ const createAccount = async () => {
|
||||
// 清除之前的错误
|
||||
errors.value.name = ''
|
||||
errors.value.accessToken = ''
|
||||
errors.value.apiUrl = ''
|
||||
errors.value.apiKey = ''
|
||||
|
||||
let hasError = false
|
||||
|
||||
@@ -575,7 +780,17 @@ const createAccount = async () => {
|
||||
hasError = true
|
||||
}
|
||||
|
||||
if (form.value.addType === 'manual' && (!form.value.accessToken || form.value.accessToken.trim() === '')) {
|
||||
// Claude Console 验证
|
||||
if (form.value.platform === 'claude-console') {
|
||||
if (!form.value.apiUrl || form.value.apiUrl.trim() === '') {
|
||||
errors.value.apiUrl = '请填写 API URL'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.apiKey || form.value.apiKey.trim() === '') {
|
||||
errors.value.apiKey = '请填写 API Key'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.addType === 'manual' && (!form.value.accessToken || form.value.accessToken.trim() === '')) {
|
||||
errors.value.accessToken = '请填写 Access Token'
|
||||
hasError = true
|
||||
}
|
||||
@@ -611,6 +826,7 @@ const createAccount = async () => {
|
||||
expiresAt: Date.now() + expiresInMs,
|
||||
scopes: ['user:inference']
|
||||
}
|
||||
data.priority = form.value.priority || 50
|
||||
} else if (form.value.platform === 'gemini') {
|
||||
// Gemini手动模式需要构建geminiOauth对象
|
||||
const expiresInMs = form.value.refreshToken
|
||||
@@ -628,11 +844,23 @@ const createAccount = async () => {
|
||||
if (form.value.projectId) {
|
||||
data.projectId = form.value.projectId
|
||||
}
|
||||
} else if (form.value.platform === 'claude-console') {
|
||||
// Claude Console 账户特定数据
|
||||
data.apiUrl = form.value.apiUrl
|
||||
data.apiKey = form.value.apiKey
|
||||
data.priority = form.value.priority || 50
|
||||
data.supportedModels = form.value.supportedModels
|
||||
? form.value.supportedModels.split('\n').filter(m => m.trim())
|
||||
: []
|
||||
data.userAgent = form.value.userAgent || null
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
}
|
||||
|
||||
let result
|
||||
if (form.value.platform === 'claude') {
|
||||
result = await accountsStore.createClaudeAccount(data)
|
||||
} else if (form.value.platform === 'claude-console') {
|
||||
result = await accountsStore.createClaudeConsoleAccount(data)
|
||||
} else {
|
||||
result = await accountsStore.createGeminiAccount(data)
|
||||
}
|
||||
@@ -721,8 +949,29 @@ const updateAccount = async () => {
|
||||
data.projectId = form.value.projectId
|
||||
}
|
||||
|
||||
// Claude 官方账号优先级更新
|
||||
if (props.account.platform === 'claude') {
|
||||
data.priority = form.value.priority || 50
|
||||
}
|
||||
|
||||
// Claude Console 特定更新
|
||||
if (props.account.platform === 'claude-console') {
|
||||
data.apiUrl = form.value.apiUrl
|
||||
if (form.value.apiKey) {
|
||||
data.apiKey = form.value.apiKey
|
||||
}
|
||||
data.priority = form.value.priority || 50
|
||||
data.supportedModels = form.value.supportedModels
|
||||
? form.value.supportedModels.split('\n').filter(m => m.trim())
|
||||
: []
|
||||
data.userAgent = form.value.userAgent || null
|
||||
data.rateLimitDuration = form.value.rateLimitDuration || 60
|
||||
}
|
||||
|
||||
if (props.account.platform === 'claude') {
|
||||
await accountsStore.updateClaudeAccount(props.account.id, data)
|
||||
} else if (props.account.platform === 'claude-console') {
|
||||
await accountsStore.updateClaudeConsoleAccount(props.account.id, data)
|
||||
} else {
|
||||
await accountsStore.updateGeminiAccount(props.account.id, data)
|
||||
}
|
||||
@@ -749,6 +998,46 @@ watch(() => form.value.accessToken, () => {
|
||||
}
|
||||
})
|
||||
|
||||
// 监听API URL变化,清除错误
|
||||
watch(() => form.value.apiUrl, () => {
|
||||
if (errors.value.apiUrl && form.value.apiUrl?.trim()) {
|
||||
errors.value.apiUrl = ''
|
||||
}
|
||||
})
|
||||
|
||||
// 监听API Key变化,清除错误
|
||||
watch(() => form.value.apiKey, () => {
|
||||
if (errors.value.apiKey && form.value.apiKey?.trim()) {
|
||||
errors.value.apiKey = ''
|
||||
}
|
||||
})
|
||||
|
||||
// 监听平台变化,重置表单
|
||||
watch(() => form.value.platform, (newPlatform) => {
|
||||
if (newPlatform === 'claude-console') {
|
||||
form.value.addType = 'manual' // Claude Console 只支持手动模式
|
||||
}
|
||||
})
|
||||
|
||||
// 添加预设模型
|
||||
const addPresetModel = (modelName) => {
|
||||
// 获取当前模型列表
|
||||
const currentModels = form.value.supportedModels
|
||||
? form.value.supportedModels.split('\n').filter(m => m.trim())
|
||||
: []
|
||||
|
||||
// 检查是否已存在
|
||||
if (currentModels.includes(modelName)) {
|
||||
showToast(`模型 ${modelName} 已存在`, 'info')
|
||||
return
|
||||
}
|
||||
|
||||
// 添加到列表
|
||||
currentModels.push(modelName)
|
||||
form.value.supportedModels = currentModels.join('\n')
|
||||
showToast(`已添加模型 ${modelName}`, 'success')
|
||||
}
|
||||
|
||||
// 监听账户变化,更新表单
|
||||
watch(() => props.account, (newAccount) => {
|
||||
if (newAccount) {
|
||||
@@ -780,7 +1069,14 @@ watch(() => props.account, (newAccount) => {
|
||||
projectId: newAccount.projectId || '',
|
||||
accessToken: '',
|
||||
refreshToken: '',
|
||||
proxy: proxyConfig
|
||||
proxy: proxyConfig,
|
||||
// Claude Console 特定字段
|
||||
apiUrl: newAccount.apiUrl || '',
|
||||
apiKey: '', // 编辑模式不显示现有的 API Key
|
||||
priority: newAccount.priority || 50,
|
||||
supportedModels: newAccount.supportedModels?.join('\n') || '',
|
||||
userAgent: newAccount.userAgent || '',
|
||||
rateLimitDuration: newAccount.rateLimitDuration || 60
|
||||
}
|
||||
}
|
||||
}, { immediate: true })
|
||||
|
||||
@@ -5,6 +5,7 @@ import { apiClient } from '@/config/api'
|
||||
export const useAccountsStore = defineStore('accounts', () => {
|
||||
// 状态
|
||||
const claudeAccounts = ref([])
|
||||
const claudeConsoleAccounts = ref([])
|
||||
const geminiAccounts = ref([])
|
||||
const loading = ref(false)
|
||||
const error = ref(null)
|
||||
@@ -32,6 +33,25 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Claude Console账户列表
|
||||
const fetchClaudeConsoleAccounts = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.get('/admin/claude-console-accounts')
|
||||
if (response.success) {
|
||||
claudeConsoleAccounts.value = response.data || []
|
||||
} else {
|
||||
throw new Error(response.message || '获取Claude Console账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取Gemini账户列表
|
||||
const fetchGeminiAccounts = async () => {
|
||||
loading.value = true
|
||||
@@ -58,6 +78,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
fetchClaudeAccounts(),
|
||||
fetchClaudeConsoleAccounts(),
|
||||
fetchGeminiAccounts()
|
||||
])
|
||||
} catch (err) {
|
||||
@@ -88,6 +109,26 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Claude Console账户
|
||||
const createClaudeConsoleAccount = async (data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.post('/admin/claude-console-accounts', data)
|
||||
if (response.success) {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
return response.data
|
||||
} else {
|
||||
throw new Error(response.message || '创建Claude Console账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 创建Gemini账户
|
||||
const createGeminiAccount = async (data) => {
|
||||
loading.value = true
|
||||
@@ -128,6 +169,26 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Claude Console账户
|
||||
const updateClaudeConsoleAccount = async (id, data) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const response = await apiClient.put(`/admin/claude-console-accounts/${id}`, data)
|
||||
if (response.success) {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
return response
|
||||
} else {
|
||||
throw new Error(response.message || '更新Claude Console账户失败')
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err.message
|
||||
throw err
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Gemini账户
|
||||
const updateGeminiAccount = async (id, data) => {
|
||||
loading.value = true
|
||||
@@ -153,14 +214,21 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const endpoint = platform === 'claude'
|
||||
? `/admin/claude-accounts/${id}/toggle`
|
||||
: `/admin/gemini-accounts/${id}/toggle`
|
||||
let endpoint
|
||||
if (platform === 'claude') {
|
||||
endpoint = `/admin/claude-accounts/${id}/toggle`
|
||||
} else if (platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${id}/toggle`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${id}/toggle`
|
||||
}
|
||||
|
||||
const response = await apiClient.put(endpoint)
|
||||
if (response.success) {
|
||||
if (platform === 'claude') {
|
||||
await fetchClaudeAccounts()
|
||||
} else if (platform === 'claude-console') {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
} else {
|
||||
await fetchGeminiAccounts()
|
||||
}
|
||||
@@ -181,14 +249,21 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
try {
|
||||
const endpoint = platform === 'claude'
|
||||
? `/admin/claude-accounts/${id}`
|
||||
: `/admin/gemini-accounts/${id}`
|
||||
let endpoint
|
||||
if (platform === 'claude') {
|
||||
endpoint = `/admin/claude-accounts/${id}`
|
||||
} else if (platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${id}`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${id}`
|
||||
}
|
||||
|
||||
const response = await apiClient.delete(endpoint)
|
||||
if (response.success) {
|
||||
if (platform === 'claude') {
|
||||
await fetchClaudeAccounts()
|
||||
} else if (platform === 'claude-console') {
|
||||
await fetchClaudeConsoleAccounts()
|
||||
} else {
|
||||
await fetchGeminiAccounts()
|
||||
}
|
||||
@@ -284,6 +359,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 排序账户
|
||||
const sortAccounts = (field) => {
|
||||
if (sortBy.value === field) {
|
||||
@@ -297,6 +373,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
// 重置store
|
||||
const reset = () => {
|
||||
claudeAccounts.value = []
|
||||
claudeConsoleAccounts.value = []
|
||||
geminiAccounts.value = []
|
||||
loading.value = false
|
||||
error.value = null
|
||||
@@ -307,6 +384,7 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
return {
|
||||
// State
|
||||
claudeAccounts,
|
||||
claudeConsoleAccounts,
|
||||
geminiAccounts,
|
||||
loading,
|
||||
error,
|
||||
@@ -315,11 +393,14 @@ export const useAccountsStore = defineStore('accounts', () => {
|
||||
|
||||
// Actions
|
||||
fetchClaudeAccounts,
|
||||
fetchClaudeConsoleAccounts,
|
||||
fetchGeminiAccounts,
|
||||
fetchAllAccounts,
|
||||
createClaudeAccount,
|
||||
createClaudeConsoleAccount,
|
||||
createGeminiAccount,
|
||||
updateClaudeAccount,
|
||||
updateClaudeConsoleAccount,
|
||||
updateGeminiAccount,
|
||||
toggleAccount,
|
||||
deleteAccount,
|
||||
|
||||
@@ -60,6 +60,11 @@
|
||||
<i v-if="accountsSortBy === 'status'" :class="['fas', accountsSortOrder === 'asc' ? 'fa-sort-up' : 'fa-sort-down', 'ml-1']"></i>
|
||||
<i v-else class="fas fa-sort ml-1 text-gray-400"></i>
|
||||
</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider cursor-pointer hover:bg-gray-100" @click="sortAccounts('priority')">
|
||||
优先级
|
||||
<i v-if="accountsSortBy === 'priority'" :class="['fas', accountsSortOrder === 'asc' ? 'fa-sort-up' : 'fa-sort-down', 'ml-1']"></i>
|
||||
<i v-else class="fas fa-sort ml-1 text-gray-400"></i>
|
||||
</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">代理</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">今日使用</th>
|
||||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">会话窗口</th>
|
||||
@@ -95,13 +100,21 @@
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800">
|
||||
<i class="fas fa-robot mr-1"></i>Gemini
|
||||
</span>
|
||||
<span v-else-if="account.platform === 'claude-console'"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-purple-100 text-purple-800">
|
||||
<i class="fas fa-terminal mr-1"></i>Claude Console
|
||||
</span>
|
||||
<span v-else
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-indigo-100 text-indigo-800">
|
||||
<i class="fas fa-brain mr-1"></i>Claude
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<span v-if="account.scopes && account.scopes.length > 0"
|
||||
<span v-if="account.platform === 'claude-console'"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-green-100 text-green-800">
|
||||
<i class="fas fa-key mr-1"></i>API Key
|
||||
</span>
|
||||
<span v-else-if="account.scopes && account.scopes.length > 0"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-800">
|
||||
<i class="fas fa-lock mr-1"></i>OAuth
|
||||
</span>
|
||||
@@ -113,22 +126,43 @@
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div class="flex flex-col gap-1">
|
||||
<span :class="['inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold',
|
||||
account.status === 'blocked' ? 'bg-orange-100 text-orange-800' :
|
||||
account.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800']">
|
||||
<div :class="['w-2 h-2 rounded-full mr-2',
|
||||
account.status === 'blocked' ? 'bg-orange-500' :
|
||||
account.isActive ? 'bg-green-500' : 'bg-red-500']"></div>
|
||||
{{ account.isActive ? '正常' : '异常' }}
|
||||
{{ account.status === 'blocked' ? '已封锁' : account.isActive ? '正常' : '异常' }}
|
||||
</span>
|
||||
<span v-if="account.rateLimitStatus && account.rateLimitStatus.isRateLimited"
|
||||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-yellow-100 text-yellow-800">
|
||||
<i class="fas fa-exclamation-triangle mr-1"></i>
|
||||
限流中 ({{ account.rateLimitStatus.minutesRemaining }}分钟)
|
||||
</span>
|
||||
<span v-if="account.status === 'blocked' && account.errorMessage"
|
||||
class="text-xs text-gray-500 mt-1 max-w-xs truncate"
|
||||
:title="account.errorMessage">
|
||||
{{ account.errorMessage }}
|
||||
</span>
|
||||
<span v-if="account.accountType === 'dedicated'"
|
||||
class="text-xs text-gray-500">
|
||||
绑定: {{ account.boundApiKeysCount || 0 }} 个API Key
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<div v-if="account.platform === 'claude' || account.platform === 'claude-console'" class="flex items-center gap-2">
|
||||
<div class="w-16 bg-gray-200 rounded-full h-2">
|
||||
<div class="bg-gradient-to-r from-green-500 to-blue-600 h-2 rounded-full transition-all duration-300"
|
||||
:style="{ width: ((100 - (account.priority || 50)) + '%') }"></div>
|
||||
</div>
|
||||
<span class="text-xs text-gray-700 font-medium min-w-[20px]">
|
||||
{{ account.priority || 50 }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-else class="text-gray-400 text-sm">
|
||||
<span class="text-xs">N/A</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||||
<div v-if="formatProxyDisplay(account.proxy)" class="text-xs bg-blue-50 px-2 py-1 rounded font-mono">
|
||||
{{ formatProxyDisplay(account.proxy) }}
|
||||
@@ -251,6 +285,7 @@ import { ref, computed, onMounted, watch } from 'vue'
|
||||
import { showToast } from '@/utils/toast'
|
||||
import { apiClient } from '@/config/api'
|
||||
import { useConfirm } from '@/composables/useConfirm'
|
||||
import { useAccountsStore } from '@/stores/accounts'
|
||||
import AccountForm from '@/components/accounts/AccountForm.vue'
|
||||
import ConfirmModal from '@/components/common/ConfirmModal.vue'
|
||||
|
||||
@@ -314,8 +349,9 @@ const sortedAccounts = computed(() => {
|
||||
const loadAccounts = async () => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const [claudeData, geminiData, apiKeysData] = await Promise.all([
|
||||
const [claudeData, claudeConsoleData, geminiData, apiKeysData] = await Promise.all([
|
||||
apiClient.get('/admin/claude-accounts'),
|
||||
apiClient.get('/admin/claude-console-accounts'),
|
||||
apiClient.get('/admin/gemini-accounts'),
|
||||
apiClient.get('/admin/api-keys')
|
||||
])
|
||||
@@ -336,6 +372,14 @@ const loadAccounts = async () => {
|
||||
allAccounts.push(...claudeAccounts)
|
||||
}
|
||||
|
||||
if (claudeConsoleData.success) {
|
||||
const claudeConsoleAccounts = (claudeConsoleData.data || []).map(acc => {
|
||||
// Claude Console账户暂时不支持直接绑定
|
||||
return { ...acc, platform: 'claude-console', boundApiKeysCount: 0 }
|
||||
})
|
||||
allAccounts.push(...claudeConsoleAccounts)
|
||||
}
|
||||
|
||||
if (geminiData.success) {
|
||||
const geminiAccounts = (geminiData.data || []).map(acc => {
|
||||
// 计算每个Gemini账户绑定的API Key数量
|
||||
@@ -449,6 +493,7 @@ const formatRemainingTime = (minutes) => {
|
||||
return `${mins}分钟`
|
||||
}
|
||||
|
||||
|
||||
// 打开创建账户模态框
|
||||
const openCreateAccountModal = () => {
|
||||
showCreateAccountModal.value = true
|
||||
@@ -483,9 +528,14 @@ const deleteAccount = async (account) => {
|
||||
if (!confirmed) return
|
||||
|
||||
try {
|
||||
const endpoint = account.platform === 'claude'
|
||||
? `/admin/claude-accounts/${account.id}`
|
||||
: `/admin/gemini-accounts/${account.id}`
|
||||
let endpoint
|
||||
if (account.platform === 'claude') {
|
||||
endpoint = `/admin/claude-accounts/${account.id}`
|
||||
} else if (account.platform === 'claude-console') {
|
||||
endpoint = `/admin/claude-console-accounts/${account.id}`
|
||||
} else {
|
||||
endpoint = `/admin/gemini-accounts/${account.id}`
|
||||
}
|
||||
|
||||
const data = await apiClient.delete(endpoint)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user