Revert "Merge pull request #424 from Wangnov/feat/i18n"

This reverts commit 1d915d8327, reversing
changes made to 009f7c84f6.
This commit is contained in:
shaw
2025-09-12 09:21:53 +08:00
parent 1d915d8327
commit 9c4dc714f8
80 changed files with 7026 additions and 19087 deletions

View File

@@ -4,10 +4,10 @@
<div class="mb-4 flex flex-col gap-4 sm:mb-6">
<div>
<h3 class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100 sm:mb-2 sm:text-xl">
{{ t('accounts.title') }}
账户管理
</h3>
<p class="text-sm text-gray-600 dark:text-gray-400 sm:text-base">
{{ t('accounts.description') }}
管理您的 ClaudeGeminiOpenAIAzure OpenAIOpenAI-Responses CCR 账户及代理配置
</p>
</div>
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
@@ -23,7 +23,7 @@
icon="fa-sort-amount-down"
icon-color="text-indigo-500"
:options="sortOptions"
:placeholder="t('accounts.sortBy')"
placeholder="选择排序"
@change="sortAccounts()"
/>
</div>
@@ -38,7 +38,7 @@
icon="fa-server"
icon-color="text-blue-500"
:options="platformOptions"
:placeholder="t('accounts.selectPlatform')"
placeholder="选择平台"
@change="filterByPlatform"
/>
</div>
@@ -53,14 +53,18 @@
icon="fa-layer-group"
icon-color="text-purple-500"
:options="groupOptions"
:placeholder="t('accounts.selectGroup')"
placeholder="选择分组"
@change="filterByGroup"
/>
</div>
<!-- 刷新按钮 -->
<div class="relative">
<el-tooltip :content="t('accounts.refreshTooltip')" effect="dark" placement="bottom">
<el-tooltip
content="刷新数据 (Ctrl/⌘+点击强制刷新所有缓存)"
effect="dark"
placement="bottom"
>
<button
class="group relative flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-all duration-200 hover:border-gray-300 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:border-gray-500 sm:w-auto"
:disabled="accountsLoading"
@@ -77,7 +81,7 @@
accountsLoading ? 'fa-spinner fa-spin' : 'fa-sync-alt'
]"
/>
<span class="relative">{{ t('accounts.refresh') }}</span>
<span class="relative">刷新</span>
</button>
</el-tooltip>
</div>
@@ -89,14 +93,14 @@
@click.stop="openCreateAccountModal"
>
<i class="fas fa-plus"></i>
<span>{{ t('accounts.addAccount') }}</span>
<span>添加账户</span>
</button>
</div>
</div>
<div v-if="accountsLoading" class="py-12 text-center">
<div class="loading-spinner mx-auto mb-4" />
<p class="text-gray-500 dark:text-gray-400">{{ t('accounts.loadingAccounts') }}</p>
<p class="text-gray-500 dark:text-gray-400">正在加载账户...</p>
</div>
<div v-else-if="sortedAccounts.length === 0" class="py-12 text-center">
@@ -105,10 +109,8 @@
>
<i class="fas fa-user-circle text-xl text-gray-400" />
</div>
<p class="text-lg text-gray-500 dark:text-gray-400">{{ t('accounts.noAccounts') }}</p>
<p class="mt-2 text-sm text-gray-400 dark:text-gray-500">
{{ t('accounts.noAccountsHint') }}
</p>
<p class="text-lg text-gray-500 dark:text-gray-400">暂无账户</p>
<p class="mt-2 text-sm text-gray-400 dark:text-gray-500">点击上方按钮添加您的第一个账户</p>
</div>
<!-- 桌面端表格视图 -->
@@ -120,7 +122,7 @@
class="w-[22%] min-w-[180px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
@click="sortAccounts('name')"
>
{{ t('accounts.name') }}
名称
<i
v-if="accountsSortBy === 'name'"
:class="[
@@ -135,7 +137,7 @@
class="w-[15%] min-w-[120px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
@click="sortAccounts('platform')"
>
{{ t('accounts.platformType') }}
平台/类型
<i
v-if="accountsSortBy === 'platform'"
:class="[
@@ -150,7 +152,7 @@
class="w-[12%] min-w-[100px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
@click="sortAccounts('status')"
>
{{ t('accounts.status') }}
状态
<i
v-if="accountsSortBy === 'status'"
:class="[
@@ -165,7 +167,7 @@
class="w-[8%] min-w-[80px] cursor-pointer px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-600"
@click="sortAccounts('priority')"
>
{{ t('accounts.priority') }}
优先级
<i
v-if="accountsSortBy === 'priority'"
:class="[
@@ -179,40 +181,40 @@
<th
class="w-[10%] min-w-[100px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
>
{{ t('accounts.proxy') }}
代理
</th>
<th
class="w-[10%] min-w-[90px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
>
{{ t('accounts.dailyUsage') }}
今日使用
</th>
<th
class="w-[10%] min-w-[100px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
>
<div class="flex items-center gap-2">
<span>{{ t('accounts.sessionWindow') }}</span>
<span>会话窗口</span>
<el-tooltip placement="top">
<template #content>
<div class="space-y-2">
<div>{{ t('accounts.sessionWindowTooltip.title') }}</div>
<div>会话窗口进度表示5小时窗口的时间进度</div>
<div class="space-y-1 text-xs">
<div class="flex items-center gap-2">
<div
class="h-2 w-16 rounded bg-gradient-to-r from-blue-500 to-indigo-600"
></div>
<span>{{ t('accounts.sessionWindowTooltip.normal') }}</span>
<span>正常请求正常处理</span>
</div>
<div class="flex items-center gap-2">
<div
class="h-2 w-16 rounded bg-gradient-to-r from-yellow-500 to-orange-500"
></div>
<span>{{ t('accounts.sessionWindowTooltip.warning') }}</span>
<span>警告接近限制</span>
</div>
<div class="flex items-center gap-2">
<div
class="h-2 w-16 rounded bg-gradient-to-r from-red-500 to-red-600"
></div>
<span>{{ t('accounts.sessionWindowTooltip.rejected') }}</span>
<span>拒绝达到速率限制</span>
</div>
</div>
</div>
@@ -226,12 +228,12 @@
<th
class="w-[8%] min-w-[80px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
>
{{ t('accounts.lastUsed') }}
最后使用
</th>
<th
class="w-[15%] min-w-[180px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
>
{{ t('accounts.actions') }}
操作
</th>
</tr>
</thead>
@@ -256,19 +258,19 @@
v-if="account.accountType === 'dedicated'"
class="inline-flex items-center rounded-full bg-purple-100 px-2 py-0.5 text-xs font-medium text-purple-800"
>
<i class="fas fa-lock mr-1" />{{ t('accounts.dedicated') }}
<i class="fas fa-lock mr-1" />专属
</span>
<span
v-else-if="account.accountType === 'group'"
class="inline-flex items-center rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-800"
>
<i class="fas fa-layer-group mr-1" />{{ t('accounts.groupScheduling') }}
<i class="fas fa-layer-group mr-1" />分组调度
</span>
<span
v-else
class="inline-flex items-center rounded-full bg-green-100 px-2 py-0.5 text-xs font-medium text-green-800"
>
<i class="fas fa-share-alt mr-1" />{{ t('accounts.shared') }}
<i class="fas fa-share-alt mr-1" />共享
</span>
</div>
<!-- 显示所有分组 - 换行显示 -->
@@ -280,7 +282,7 @@
v-for="group in account.groupInfos"
:key="group.id"
class="inline-flex items-center rounded-full bg-gray-100 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-400"
:title="t('accounts.belongsToGroup', { name: group.name })"
:title="`所属分组: ${group.name}`"
>
<i class="fas fa-folder mr-1" />{{ group.name }}
</span>
@@ -388,9 +390,7 @@
class="flex items-center gap-1.5 rounded-lg border border-gray-200 bg-gradient-to-r from-gray-100 to-gray-200 px-2.5 py-1"
>
<i class="fas fa-question text-xs text-gray-700" />
<span class="text-xs font-semibold text-gray-800">{{
t('accounts.unknown')
}}</span>
<span class="text-xs font-semibold text-gray-800">未知</span>
</div>
</div>
</td>
@@ -426,14 +426,14 @@
/>
{{
account.status === 'blocked'
? t('accounts.blocked')
? '已封锁'
: account.status === 'unauthorized'
? t('accounts.abnormal')
? '异常'
: account.status === 'temp_error'
? t('accounts.tempError')
? '临时异常'
: account.isActive
? t('accounts.normal')
: t('accounts.abnormal')
? '正常'
: '异常'
}}
</span>
<span
@@ -444,18 +444,14 @@
class="inline-flex items-center rounded-full bg-yellow-100 px-3 py-1 text-xs font-semibold text-yellow-800"
>
<i class="fas fa-exclamation-triangle mr-1" />
{{ t('accounts.rateLimited') }}
限流中
<span
v-if="
account.rateLimitStatus &&
typeof account.rateLimitStatus === 'object' &&
account.rateLimitStatus.minutesRemaining > 0
"
>({{
t('accounts.rateLimitTime', {
time: formatRateLimitTime(account.rateLimitStatus.minutesRemaining)
})
}})</span
>({{ formatRateLimitTime(account.rateLimitStatus.minutesRemaining) }})</span
>
</span>
<span
@@ -463,7 +459,7 @@
class="inline-flex items-center rounded-full bg-gray-100 px-3 py-1 text-xs font-semibold text-gray-700"
>
<i class="fas fa-pause-circle mr-1" />
{{ t('accounts.notSchedulable') }}
不可调度
<el-tooltip
v-if="getSchedulableReason(account)"
:content="getSchedulableReason(account)"
@@ -484,7 +480,7 @@
v-if="account.accountType === 'dedicated'"
class="text-xs text-gray-500 dark:text-gray-400"
>
{{ t('accounts.bound', { count: account.boundApiKeysCount || 0 }) }}
绑定: {{ account.boundApiKeysCount || 0 }} 个API Key
</span>
</div>
</td>
@@ -524,14 +520,14 @@
>
{{ formatProxyDisplay(account.proxy) }}
</div>
<div v-else class="text-gray-400">{{ t('accounts.noProxy') }}</div>
<div v-else class="text-gray-400">无代理</div>
</td>
<td class="whitespace-nowrap px-3 py-4 text-sm">
<div v-if="account.usage && account.usage.daily" class="space-y-1">
<div class="flex items-center gap-2">
<div class="h-2 w-2 rounded-full bg-blue-500" />
<span class="text-sm font-medium text-gray-900 dark:text-gray-100"
>{{ account.usage.daily.requests || 0 }} {{ t('accounts.requests') }}</span
>{{ account.usage.daily.requests || 0 }} </span
>
</div>
<div class="flex items-center gap-2">
@@ -550,10 +546,10 @@
v-if="account.usage.averages && account.usage.averages.rpm > 0"
class="text-xs text-gray-500 dark:text-gray-400"
>
{{ t('accounts.averageRpm', { rpm: account.usage.averages.rpm.toFixed(2) }) }}
平均 {{ account.usage.averages.rpm.toFixed(2) }} RPM
</div>
</div>
<div v-else class="text-xs text-gray-400">{{ t('accounts.noData') }}</div>
<div v-else class="text-xs text-gray-400">暂无数据</div>
</td>
<td class="whitespace-nowrap px-3 py-4">
<div
@@ -613,11 +609,7 @@
v-if="account.sessionWindow.remainingTime > 0"
class="font-medium text-indigo-600 dark:text-indigo-400"
>
{{
t('accounts.remaining', {
time: formatRemainingTime(account.sessionWindow.remainingTime)
})
}}
剩余 {{ formatRemainingTime(account.sessionWindow.remainingTime) }}
</div>
</div>
</div>
@@ -625,9 +617,7 @@
<div v-else-if="account.platform === 'claude-console'" class="space-y-2">
<div v-if="Number(account.dailyQuota) > 0">
<div class="flex items-center justify-between text-xs">
<span class="text-gray-600 dark:text-gray-300">{{
t('accounts.quotaProgress')
}}</span>
<span class="text-gray-600 dark:text-gray-300">额度进度</span>
<span class="font-medium text-gray-700 dark:text-gray-200">
{{ getQuotaUsagePercent(account).toFixed(1) }}%
</span>
@@ -651,10 +641,10 @@
</span>
</div>
<div class="text-xs text-gray-600 dark:text-gray-400">
{{ t('accounts.remainingQuota', { amount: formatRemainingQuota(account) }) }}
<span class="ml-2 text-gray-400">{{
t('accounts.reset', { time: account.quotaResetTime || '00:00' })
}}</span>
剩余 ${{ formatRemainingQuota(account) }}
<span class="ml-2 text-gray-400"
>重置 {{ account.quotaResetTime || '00:00' }}</span
>
</div>
</div>
<div v-else class="text-sm text-gray-400">
@@ -692,15 +682,11 @@
: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200'
]"
:disabled="account.isResetting"
:title="
account.isResetting
? t('accounts.resetting')
: t('accounts.resetStatusTooltip')
"
:title="account.isResetting ? '重置中...' : '重置所有异常状态'"
@click="resetAccountStatus(account)"
>
<i :class="['fas fa-redo', account.isResetting ? 'animate-spin' : '']" />
<span class="ml-1">{{ t('accounts.resetStatus') }}</span>
<span class="ml-1">重置状态</span>
</button>
<button
:class="[
@@ -712,33 +698,27 @@
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
]"
:disabled="account.isTogglingSchedulable"
:title="
account.schedulable
? t('accounts.disableTooltip')
: t('accounts.enableTooltip')
"
:title="account.schedulable ? '点击禁用调度' : '点击启用调度'"
@click="toggleSchedulable(account)"
>
<i :class="['fas', account.schedulable ? 'fa-toggle-on' : 'fa-toggle-off']" />
<span class="ml-1">{{
account.schedulable ? t('accounts.scheduling') : t('accounts.disabled')
}}</span>
<span class="ml-1">{{ account.schedulable ? '调度' : '停用' }}</span>
</button>
<button
class="rounded bg-blue-100 px-2.5 py-1 text-xs font-medium text-blue-700 transition-colors hover:bg-blue-200"
:title="t('accounts.editTooltip')"
:title="'编辑账户'"
@click="editAccount(account)"
>
<i class="fas fa-edit" />
<span class="ml-1">{{ t('accounts.edit') }}</span>
<span class="ml-1">编辑</span>
</button>
<button
class="rounded bg-red-100 px-2.5 py-1 text-xs font-medium text-red-700 transition-colors hover:bg-red-200"
:title="t('accounts.deleteTooltip')"
:title="'删除账户'"
@click="deleteAccount(account)"
>
<i class="fas fa-trash" />
<span class="ml-1">{{ t('accounts.delete') }}</span>
<span class="ml-1">删除</span>
</button>
</div>
</td>
@@ -819,14 +799,12 @@
<!-- 使用统计 -->
<div class="mb-3 grid grid-cols-2 gap-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('accounts.dailyUsageLabel') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">今日使用</p>
<div class="space-y-1">
<div class="flex items-center gap-1.5">
<div class="h-1.5 w-1.5 rounded-full bg-blue-500" />
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">
{{ account.usage?.daily?.requests || 0 }} {{ t('accounts.requests') }}
{{ account.usage?.daily?.requests || 0 }}
</p>
</div>
<div class="flex items-center gap-1.5">
@@ -844,9 +822,7 @@
</div>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('accounts.sessionWindowLabel') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">会话窗口</p>
<div v-if="account.usage && account.usage.sessionWindow" class="space-y-1">
<div class="flex items-center gap-1.5">
<div class="h-1.5 w-1.5 rounded-full bg-purple-500" />
@@ -878,10 +854,11 @@
>
<div class="flex items-center justify-between text-xs">
<div class="flex items-center gap-1">
<span class="font-medium text-gray-600 dark:text-gray-300">{{
t('accounts.sessionWindowLabel')
}}</span>
<el-tooltip :content="t('accounts.sessionWindowTooltipMobile')" placement="top">
<span class="font-medium text-gray-600 dark:text-gray-300">会话窗口</span>
<el-tooltip
content="会话窗口进度不代表使用量仅表示距离下一个5小时窗口的剩余时间"
placement="top"
>
<i
class="fas fa-question-circle cursor-help text-xs text-gray-400 hover:text-gray-600"
/>
@@ -913,27 +890,17 @@
v-if="account.sessionWindow.remainingTime > 0"
class="font-medium text-indigo-600"
>
{{
t('accounts.remaining', {
time: formatRemainingTime(account.sessionWindow.remainingTime)
})
}}
剩余 {{ formatRemainingTime(account.sessionWindow.remainingTime) }}
</span>
<span v-else class="text-gray-500"> {{ t('accounts.ended') }} </span>
<span v-else class="text-gray-500"> 已结束 </span>
</div>
</div>
<!-- 最后使用时间 -->
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500 dark:text-gray-400">{{
t('accounts.lastUsedLabel')
}}</span>
<span class="text-gray-500 dark:text-gray-400">最后使用</span>
<span class="text-gray-700 dark:text-gray-200">
{{
account.lastUsedAt
? formatRelativeTime(account.lastUsedAt)
: t('accounts.neverUsed')
}}
{{ account.lastUsedAt ? formatRelativeTime(account.lastUsedAt) : '从未使用' }}
</span>
</div>
@@ -942,7 +909,7 @@
v-if="account.proxyConfig && account.proxyConfig.type !== 'none'"
class="flex items-center justify-between text-xs"
>
<span class="text-gray-500 dark:text-gray-400">{{ t('accounts.proxyLabel') }}</span>
<span class="text-gray-500 dark:text-gray-400">代理</span>
<span class="text-gray-700 dark:text-gray-200">
{{ account.proxyConfig.type.toUpperCase() }}
</span>
@@ -950,9 +917,7 @@
<!-- 调度优先级 -->
<div class="flex items-center justify-between text-xs">
<span class="text-gray-500 dark:text-gray-400">{{
t('accounts.priorityLabel')
}}</span>
<span class="text-gray-500 dark:text-gray-400">优先级</span>
<span class="font-medium text-gray-700 dark:text-gray-200">
{{ account.priority || 50 }}
</span>
@@ -972,7 +937,7 @@
@click="toggleSchedulable(account)"
>
<i :class="['fas', account.schedulable ? 'fa-pause' : 'fa-play']" />
{{ account.schedulable ? t('accounts.pause') : t('accounts.enable') }}
{{ account.schedulable ? '暂停' : '启用' }}
</button>
<button
@@ -980,7 +945,7 @@
@click="editAccount(account)"
>
<i class="fas fa-edit mr-1" />
{{ t('accounts.edit') }}
编辑
</button>
<button
@@ -1036,7 +1001,6 @@
<script setup>
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { showToast } from '@/utils/toast'
import { apiClient } from '@/config/api'
import { useConfirm } from '@/composables/useConfirm'
@@ -1045,9 +1009,6 @@ import CcrAccountForm from '@/components/accounts/CcrAccountForm.vue'
import ConfirmModal from '@/components/common/ConfirmModal.vue'
import CustomDropdown from '@/components/common/CustomDropdown.vue'
// 国际化
const { t } = useI18n()
// 使用确认弹窗
const { showConfirmModal, confirmOptions, showConfirm, handleConfirm, handleCancel } = useConfirm()
@@ -1069,30 +1030,30 @@ const groupMembersLoaded = ref(false)
const accountGroupMap = ref(new Map()) // Map<accountId, Array<groupInfo>>
// 下拉选项数据
const sortOptions = computed(() => [
{ value: 'name', label: t('accounts.sortByName'), icon: 'fa-font' },
{ value: 'dailyTokens', label: t('accounts.sortByDailyTokens'), icon: 'fa-coins' },
{ value: 'dailyRequests', label: t('accounts.sortByDailyRequests'), icon: 'fa-chart-line' },
{ value: 'totalTokens', label: t('accounts.sortByTotalTokens'), icon: 'fa-database' },
{ value: 'lastUsed', label: t('accounts.sortByLastUsed'), icon: 'fa-clock' }
const sortOptions = ref([
{ value: 'name', label: '按名称排序', icon: 'fa-font' },
{ value: 'dailyTokens', label: '按今日Token排序', icon: 'fa-coins' },
{ value: 'dailyRequests', label: '按今日请求数排序', icon: 'fa-chart-line' },
{ value: 'totalTokens', label: '按总Token排序', icon: 'fa-database' },
{ value: 'lastUsed', label: '按最后使用排序', icon: 'fa-clock' }
])
const platformOptions = computed(() => [
{ value: 'all', label: t('accounts.allPlatforms'), icon: 'fa-globe' },
{ value: 'claude', label: t('accounts.claudePlatform'), icon: 'fa-brain' },
{ value: 'claude-console', label: t('accounts.claudeConsolePlatform'), icon: 'fa-terminal' },
{ value: 'gemini', label: t('accounts.geminiPlatform'), icon: 'fa-google' },
{ value: 'openai', label: t('accounts.openaiPlatform'), icon: 'fa-openai' },
{ value: 'azure_openai', label: t('accounts.azureOpenaiPlatform'), icon: 'fab fa-microsoft' },
{ value: 'bedrock', label: t('accounts.bedrockPlatform'), icon: 'fab fa-aws' },
{ value: 'openai-responses', label: t('accounts.openaiResponsesPlatform'), icon: 'fa-server' },
{ value: 'ccr', label: t('accounts.ccrPlatform'), icon: 'fa-code-branch' }
const platformOptions = ref([
{ value: 'all', label: '所有平台', icon: 'fa-globe' },
{ value: 'claude', label: 'Claude', icon: 'fa-brain' },
{ value: 'claude-console', label: 'Claude Console', icon: 'fa-terminal' },
{ value: 'gemini', label: 'Gemini', icon: 'fa-google' },
{ value: 'openai', label: 'OpenAi', icon: 'fa-openai' },
{ value: 'azure_openai', label: 'Azure OpenAI', icon: 'fab fa-microsoft' },
{ value: 'bedrock', label: 'Bedrock', icon: 'fab fa-aws' },
{ value: 'openai-responses', label: 'OpenAI-Responses', icon: 'fa-server' },
{ value: 'ccr', label: 'CCR', icon: 'fa-code-branch' }
])
const groupOptions = computed(() => {
const options = [
{ value: 'all', label: t('accounts.allAccounts'), icon: 'fa-globe' },
{ value: 'ungrouped', label: t('accounts.ungroupedAccounts'), icon: 'fa-user' }
{ value: 'all', label: '所有账户', icon: 'fa-globe' },
{ value: 'ungrouped', label: '未分组账户', icon: 'fa-user' }
]
accountGroups.value.forEach((group) => {
options.push({
@@ -1432,7 +1393,7 @@ const loadAccounts = async (forceReload = false) => {
accounts.value = filteredAccounts
} catch (error) {
showToast(t('accounts.loadAccountsFailed'), 'error')
showToast('加载账户失败', 'error')
} finally {
accountsLoading.value = false
}
@@ -1464,16 +1425,16 @@ const formatNumber = (num) => {
// 格式化最后使用时间
const formatLastUsed = (dateString) => {
if (!dateString) return t('accounts.neverUsed')
if (!dateString) return '从未使用'
const date = new Date(dateString)
const now = new Date()
const diff = now - date
if (diff < 60000) return t('accounts.justNow')
if (diff < 3600000) return t('accounts.minutesAgo', { minutes: Math.floor(diff / 60000) })
if (diff < 86400000) return t('accounts.hoursAgo', { hours: Math.floor(diff / 3600000) })
if (diff < 604800000) return t('accounts.daysAgo', { days: Math.floor(diff / 86400000) })
if (diff < 60000) return '刚刚'
if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`
if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`
if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`
return date.toLocaleDateString('zh-CN')
}
@@ -1570,15 +1531,15 @@ const formatSessionWindow = (windowStart, windowEnd) => {
// 格式化剩余时间
const formatRemainingTime = (minutes) => {
if (!minutes || minutes <= 0) return t('accounts.ended')
if (!minutes || minutes <= 0) return '已结束'
const hours = Math.floor(minutes / 60)
const mins = minutes % 60
if (hours > 0) {
return t('accounts.hoursAndMinutes', { hours, minutes: mins })
return `${hours}小时${mins}分钟`
}
return t('accounts.minutesOnly', { minutes: mins })
return `${mins}分钟`
}
// 格式化限流时间(支持显示天数)
@@ -1598,18 +1559,18 @@ const formatRateLimitTime = (minutes) => {
if (days > 0) {
// 超过1天显示天数和小时
if (hours > 0) {
return t('accounts.daysAndHours', { days, hours })
return `${days}天${hours}小时`
}
return t('accounts.daysOnly', { days })
return `${days}天`
} else if (hours > 0) {
// 超过1小时但不到1天显示小时和分钟
if (mins > 0) {
return t('accounts.hoursAndMinutes', { hours, minutes: mins })
return `${hours}小时${mins}分钟`
}
return t('accounts.hoursOnly', { hours })
return `${hours}小时`
} else {
// 不到1小时只显示分钟
return t('accounts.minutesOnly', { minutes: mins })
return `${mins}分钟`
}
}
@@ -1645,15 +1606,18 @@ const deleteAccount = async (account) => {
).length
if (boundKeysCount > 0) {
showToast(t('accounts.cannotDeleteBoundAccount', { count: boundKeysCount }), 'error')
showToast(
`无法删除此账号,有 ${boundKeysCount} 个API Key绑定到此账号请先解绑所有API Key`,
'error'
)
return
}
const confirmed = await showConfirm(
t('accounts.deleteAccountTitle'),
t('accounts.deleteAccountMessage', { name: account.name }),
t('accounts.deleteAccountButton'),
t('accounts.deleteAccountCancel')
'删除账户',
`确定要删除账户 "${account.name}" \n\n此操作不可恢复`,
'删除',
'取消'
)
if (!confirmed) return
@@ -1681,15 +1645,15 @@ const deleteAccount = async (account) => {
const data = await apiClient.delete(endpoint)
if (data.success) {
showToast(t('accounts.accountDeleted'), 'success')
showToast('账户已删除', 'success')
// 清空分组成员缓存因为账户可能从分组中移除
groupMembersLoaded.value = false
loadAccounts()
} else {
showToast(data.message || t('accounts.deleteFailed'), 'error')
showToast(data.message || '删除失败', 'error')
}
} catch (error) {
showToast(t('accounts.deleteFailed'), 'error')
showToast('删除失败', 'error')
}
}
@@ -1700,13 +1664,13 @@ const resetAccountStatus = async (account) => {
let confirmed = false
if (window.showConfirm) {
confirmed = await window.showConfirm(
t('accounts.resetStatusConfirmTitle'),
t('accounts.resetStatusConfirmMessage'),
t('accounts.resetStatusConfirmButton'),
t('accounts.resetStatusCancelButton')
'重置账户状态',
'确定要重置此账户的所有异常状态吗这将清除限流状态、401错误计数等所有异常标记。',
'确定重置',
'取消'
)
} else {
confirmed = confirm(t('accounts.resetStatusConfirmMessage'))
confirmed = confirm('确定要重置此账户的所有异常状态吗?')
}
if (!confirmed) return
@@ -1727,7 +1691,7 @@ const resetAccountStatus = async (account) => {
} else if (account.platform === 'ccr') {
endpoint = `/admin/ccr-accounts/${account.id}/reset-status`
} else {
showToast(t('accounts.unsupportedAccountTypeReset'), 'error')
showToast('不支持的账户类型', 'error')
account.isResetting = false
return
}
@@ -1735,14 +1699,14 @@ const resetAccountStatus = async (account) => {
const data = await apiClient.post(endpoint)
if (data.success) {
showToast(t('accounts.statusResetSuccess'), 'success')
showToast('账户状态已重置', 'success')
// 强制刷新,绕过前端缓存,确保最终一致性
loadAccounts(true)
} else {
showToast(data.message || t('accounts.statusResetFailed'), 'error')
showToast(data.message || '状态重置失败', 'error')
}
} catch (error) {
showToast(t('accounts.statusResetFailed'), 'error')
showToast('状态重置失败', 'error')
} finally {
account.isResetting = false
}
@@ -1773,7 +1737,7 @@ const toggleSchedulable = async (account) => {
} else if (account.platform === 'ccr') {
endpoint = `/admin/ccr-accounts/${account.id}/toggle-schedulable`
} else {
showToast(t('accounts.unsupportedAccountType'), 'warning')
showToast('该账户类型暂不支持调度控制', 'warning')
return
}
@@ -1781,15 +1745,12 @@ const toggleSchedulable = async (account) => {
if (data.success) {
account.schedulable = data.schedulable
showToast(
data.schedulable ? t('accounts.enabledScheduling') : t('accounts.disabledScheduling'),
'success'
)
showToast(data.schedulable ? '已启用调度' : '已禁用调度', 'success')
} else {
showToast(data.message || t('accounts.operationFailed'), 'error')
showToast(data.message || '操作失败', 'error')
}
} catch (error) {
showToast(t('accounts.schedulingToggleFailed'), 'error')
showToast('切换调度状态失败', 'error')
} finally {
account.isTogglingSchedulable = false
}
@@ -1798,7 +1759,7 @@ const toggleSchedulable = async (account) => {
// 处理创建成功
const handleCreateSuccess = () => {
showCreateAccountModal.value = false
showToast(t('accounts.accountCreateSuccess'), 'success')
showToast('账户创建成功', 'success')
// 清空缓存,因为可能涉及分组关系变化
clearCache()
loadAccounts()
@@ -1807,7 +1768,7 @@ const handleCreateSuccess = () => {
// 处理编辑成功
const handleEditSuccess = () => {
showEditAccountModal.value = false
showToast(t('accounts.accountUpdateSuccess'), 'success')
showToast('账户更新成功', 'success')
// 清空分组成员缓存,因为账户类型和分组可能发生变化
groupMembersLoaded.value = false
loadAccounts()
@@ -1849,11 +1810,11 @@ const getClaudeAccountType = (account) => {
// 根据 has_claude_max 和 has_claude_pro 判断
if (info.hasClaudeMax === true) {
return t('accounts.claudeMax')
return 'Claude Max'
} else if (info.hasClaudePro === true) {
return t('accounts.claudePro')
return 'Claude Pro'
} else {
return t('accounts.claudeFree')
return 'Claude Free'
}
} catch (e) {
// 解析失败,返回默认值
@@ -1872,13 +1833,13 @@ const getSchedulableReason = (account) => {
// Claude Console 账户的错误状态
if (account.platform === 'claude-console') {
if (account.status === 'unauthorized') {
return t('accounts.invalidApiKey')
return 'API Key无效或已过期401错误'
}
if (account.overloadStatus === 'overloaded') {
return t('accounts.serviceOverload')
return '服务过载529错误'
}
if (account.rateLimitStatus === 'limited') {
return t('accounts.rateLimitTriggered')
return '触发限流429错误'
}
if (account.status === 'blocked' && account.errorMessage) {
return account.errorMessage
@@ -1888,7 +1849,7 @@ const getSchedulableReason = (account) => {
// Claude 官方账户的错误状态
if (account.platform === 'claude') {
if (account.status === 'unauthorized') {
return t('accounts.authFailed')
return '认证失败401错误'
}
if (account.status === 'temp_error' && account.errorMessage) {
return account.errorMessage
@@ -1897,7 +1858,7 @@ const getSchedulableReason = (account) => {
return account.errorMessage
}
if (account.isRateLimited) {
return t('accounts.rateLimitTriggered')
return '触发限流429错误'
}
// 自动停止调度的原因
if (account.stoppedReason) {
@@ -1951,15 +1912,15 @@ const getSchedulableReason = (account) => {
}
// 默认为手动停止
return t('accounts.manualStop')
return '手动停止调度'
}
// 获取账户状态文本
const getAccountStatusText = (account) => {
// 检查是否被封锁
if (account.status === 'blocked') return t('accounts.blocked')
if (account.status === 'blocked') return '已封锁'
// 检查是否未授权401错误
if (account.status === 'unauthorized') return t('accounts.abnormal')
if (account.status === 'unauthorized') return '异常'
// 检查是否限流
if (
account.isRateLimited ||
@@ -1967,15 +1928,15 @@ const getAccountStatusText = (account) => {
(account.rateLimitStatus && account.rateLimitStatus.isRateLimited) ||
account.rateLimitStatus === 'limited'
)
return t('accounts.rateLimited')
return '限流中'
// 检查是否临时错误
if (account.status === 'temp_error') return t('accounts.tempError')
if (account.status === 'temp_error') return '临时异常'
// 检查是否错误
if (account.status === 'error' || !account.isActive) return t('accounts.abnormal')
if (account.status === 'error' || !account.isActive) return '错误'
// 检查是否可调度
if (account.schedulable === false) return t('accounts.disabled')
if (account.schedulable === false) return '已暂停'
// 否则正常
return t('accounts.normal')
return '正常'
}
// 获取账户状态样式类