feat: 完成三个核心组件的国际化实现

- 完成 GroupManagementModal.vue 组件国际化
  * 添加分组管理相关的所有翻译键
  * 实现创建、编辑、删除分组功能的多语言支持

- 完成 OAuthFlow.vue 组件国际化
  * 支持 Claude、Gemini、OpenAI 三个平台的授权流程
  * 修复模板中的语法错误(缺少引号)
  * 保留技术性地址不进行翻译

- 完成 ProxyConfig.vue 组件国际化
  * 添加代理配置相关的翻译键
  * 支持 SOCKS5 和 HTTP 代理类型的多语言显示

- 更新语言文件
  * 在 zh-cn.js、en.js、zh-tw.js 中添加所有新的翻译键
  * 保持三种语言文件的同步

变更统计:6 文件修改,526 行新增,116 行删除
This commit is contained in:
Wangnov
2025-09-09 19:21:02 +08:00
parent 2b40552eab
commit 2de5191c05
6 changed files with 526 additions and 116 deletions

View File

@@ -11,7 +11,7 @@
> >
<i class="fas fa-layer-group text-sm text-white sm:text-base" /> <i class="fas fa-layer-group text-sm text-white sm:text-base" />
</div> </div>
<h3 class="text-lg font-bold text-gray-900 sm:text-xl">账户分组管理</h3> <h3 class="text-lg font-bold text-gray-900 sm:text-xl">{{ t('groupManagement.title') }}</h3>
</div> </div>
<button <button
class="p-1 text-gray-400 transition-colors hover:text-gray-600" class="p-1 text-gray-400 transition-colors hover:text-gray-600"
@@ -25,26 +25,26 @@
<div class="mb-6"> <div class="mb-6">
<button class="btn btn-primary px-4 py-2" @click="showCreateForm = true"> <button class="btn btn-primary px-4 py-2" @click="showCreateForm = true">
<i class="fas fa-plus mr-2" /> <i class="fas fa-plus mr-2" />
创建新分组 {{ t('groupManagement.createNewGroup') }}
</button> </button>
</div> </div>
<!-- 创建分组表单 --> <!-- 创建分组表单 -->
<div v-if="showCreateForm" class="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4"> <div v-if="showCreateForm" class="mb-6 rounded-lg border border-blue-200 bg-blue-50 p-4">
<h4 class="mb-4 text-lg font-semibold text-gray-900">创建新分组</h4> <h4 class="mb-4 text-lg font-semibold text-gray-900">{{ t('groupManagement.createGroup') }}</h4>
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">分组名称 *</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.groupNameRequired') }}</label>
<input <input
v-model="createForm.name" v-model="createForm.name"
class="form-input w-full" class="form-input w-full"
placeholder="输入分组名称" :placeholder="t('groupManagement.groupNamePlaceholder')"
type="text" type="text"
/> />
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">平台类型 *</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.platformTypeRequired') }}</label>
<div class="flex gap-4"> <div class="flex gap-4">
<label class="flex cursor-pointer items-center"> <label class="flex cursor-pointer items-center">
<input v-model="createForm.platform" class="mr-2" type="radio" value="claude" /> <input v-model="createForm.platform" class="mr-2" type="radio" value="claude" />
@@ -62,11 +62,11 @@
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">描述 (可选)</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.descriptionOptional') }}</label>
<textarea <textarea
v-model="createForm.description" v-model="createForm.description"
class="form-input w-full resize-none" class="form-input w-full resize-none"
placeholder="分组描述..." :placeholder="t('groupManagement.descriptionPlaceholder')"
rows="2" rows="2"
/> />
</div> </div>
@@ -78,9 +78,9 @@
@click="createGroup" @click="createGroup"
> >
<div v-if="creating" class="loading-spinner mr-2" /> <div v-if="creating" class="loading-spinner mr-2" />
{{ creating ? '创建中...' : '创建' }} {{ creating ? t('groupManagement.creating') : t('groupManagement.create') }}
</button> </button>
<button class="btn btn-secondary px-4 py-2" @click="cancelCreate">取消</button> <button class="btn btn-secondary px-4 py-2" @click="cancelCreate">{{ t('groupManagement.cancel') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -89,12 +89,12 @@
<div class="space-y-4"> <div class="space-y-4">
<div v-if="loading" class="py-8 text-center"> <div v-if="loading" class="py-8 text-center">
<div class="loading-spinner-lg mx-auto mb-4" /> <div class="loading-spinner-lg mx-auto mb-4" />
<p class="text-gray-500">加载中...</p> <p class="text-gray-500">{{ t('groupManagement.loading') }}</p>
</div> </div>
<div v-else-if="groups.length === 0" class="rounded-lg bg-gray-50 py-8 text-center"> <div v-else-if="groups.length === 0" class="rounded-lg bg-gray-50 py-8 text-center">
<i class="fas fa-layer-group mb-4 text-4xl text-gray-300" /> <i class="fas fa-layer-group mb-4 text-4xl text-gray-300" />
<p class="text-gray-500">暂无分组</p> <p class="text-gray-500">{{ t('groupManagement.noGroups') }}</p>
</div> </div>
<div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2"> <div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2">
@@ -109,7 +109,7 @@
{{ group.name }} {{ group.name }}
</h4> </h4>
<p class="mt-1 text-sm text-gray-500"> <p class="mt-1 text-sm text-gray-500">
{{ group.description || '暂无描述' }} {{ group.description || t('groupManagement.noDescription') }}
</p> </p>
</div> </div>
<div class="ml-4 flex items-center gap-2"> <div class="ml-4 flex items-center gap-2">
@@ -138,7 +138,7 @@
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<span> <span>
<i class="fas fa-users mr-1" /> <i class="fas fa-users mr-1" />
{{ group.memberCount || 0 }} 个成员 {{ group.memberCount || 0 }}{{ t('groupManagement.membersCount') }}
</span> </span>
<span> <span>
<i class="fas fa-clock mr-1" /> <i class="fas fa-clock mr-1" />
@@ -148,7 +148,7 @@
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<button <button
class="text-blue-600 transition-colors hover:text-blue-800" class="text-blue-600 transition-colors hover:text-blue-800"
title="编辑" :title="t('groupManagement.edit')"
@click="editGroup(group)" @click="editGroup(group)"
> >
<i class="fas fa-edit" /> <i class="fas fa-edit" />
@@ -156,7 +156,7 @@
<button <button
class="text-red-600 transition-colors hover:text-red-800" class="text-red-600 transition-colors hover:text-red-800"
:disabled="group.memberCount > 0" :disabled="group.memberCount > 0"
title="删除" :title="t('groupManagement.delete')"
@click="deleteGroup(group)" @click="deleteGroup(group)"
> >
<i class="fas fa-trash" /> <i class="fas fa-trash" />
@@ -176,7 +176,7 @@
> >
<div class="modal-content w-full max-w-lg p-4 sm:p-6"> <div class="modal-content w-full max-w-lg p-4 sm:p-6">
<div class="mb-4 flex items-center justify-between"> <div class="mb-4 flex items-center justify-between">
<h3 class="text-lg font-bold text-gray-900">编辑分组</h3> <h3 class="text-lg font-bold text-gray-900">{{ t('groupManagement.editGroup') }}</h3>
<button class="text-gray-400 transition-colors hover:text-gray-600" @click="cancelEdit"> <button class="text-gray-400 transition-colors hover:text-gray-600" @click="cancelEdit">
<i class="fas fa-times" /> <i class="fas fa-times" />
</button> </button>
@@ -184,17 +184,17 @@
<div class="space-y-4"> <div class="space-y-4">
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">分组名称 *</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.groupNameRequired') }}</label>
<input <input
v-model="editForm.name" v-model="editForm.name"
class="form-input w-full" class="form-input w-full"
placeholder="输入分组名称" :placeholder="t('groupManagement.groupNamePlaceholder')"
type="text" type="text"
/> />
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">平台类型</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.platformTypeLabel') }}</label>
<div class="rounded-lg bg-gray-100 px-3 py-2 text-sm text-gray-600"> <div class="rounded-lg bg-gray-100 px-3 py-2 text-sm text-gray-600">
{{ {{
editForm.platform === 'claude' editForm.platform === 'claude'
@@ -203,16 +203,16 @@
? 'Gemini' ? 'Gemini'
: 'OpenAI' : 'OpenAI'
}} }}
<span class="ml-2 text-xs text-gray-500">(不可修改)</span> <span class="ml-2 text-xs text-gray-500">{{ t('groupManagement.cannotModify') }}</span>
</div> </div>
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-semibold text-gray-700">描述 (可选)</label> <label class="mb-2 block text-sm font-semibold text-gray-700">{{ t('groupManagement.descriptionOptional') }}</label>
<textarea <textarea
v-model="editForm.description" v-model="editForm.description"
class="form-input w-full resize-none" class="form-input w-full resize-none"
placeholder="分组描述..." :placeholder="t('groupManagement.descriptionPlaceholder')"
rows="2" rows="2"
/> />
</div> </div>
@@ -224,9 +224,9 @@
@click="updateGroup" @click="updateGroup"
> >
<div v-if="updating" class="loading-spinner mr-2" /> <div v-if="updating" class="loading-spinner mr-2" />
{{ updating ? '更新中...' : '更新' }} {{ updating ? t('groupManagement.updating') : t('groupManagement.update') }}
</button> </button>
<button class="btn btn-secondary flex-1 px-4 py-2" @click="cancelEdit">取消</button> <button class="btn btn-secondary flex-1 px-4 py-2" @click="cancelEdit">{{ t('groupManagement.cancel') }}</button>
</div> </div>
</div> </div>
</div> </div>
@@ -236,9 +236,12 @@
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import { showToast } from '@/utils/toast' import { showToast } from '@/utils/toast'
import { apiClient } from '@/config/api' import { apiClient } from '@/config/api'
const { t } = useI18n()
const emit = defineEmits(['close', 'refresh']) const emit = defineEmits(['close', 'refresh'])
const show = ref(true) const show = ref(true)
@@ -278,7 +281,7 @@ const loadGroups = async () => {
const response = await apiClient.get('/admin/account-groups') const response = await apiClient.get('/admin/account-groups')
groups.value = response.data || [] groups.value = response.data || []
} catch (error) { } catch (error) {
showToast('加载分组列表失败', 'error') showToast(t('groupManagement.loadGroupsFailed'), 'error')
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -287,7 +290,7 @@ const loadGroups = async () => {
// 创建分组 // 创建分组
const createGroup = async () => { const createGroup = async () => {
if (!createForm.value.name || !createForm.value.platform) { if (!createForm.value.name || !createForm.value.platform) {
showToast('请填写必填项', 'error') showToast(t('groupManagement.fillRequiredFields'), 'error')
return return
} }
@@ -299,12 +302,12 @@ const createGroup = async () => {
description: createForm.value.description description: createForm.value.description
}) })
showToast('分组创建成功', 'success') showToast(t('groupManagement.groupCreated'), 'success')
cancelCreate() cancelCreate()
await loadGroups() await loadGroups()
emit('refresh') emit('refresh')
} catch (error) { } catch (error) {
showToast(error.response?.data?.error || '创建分组失败', 'error') showToast(error.response?.data?.error || t('groupManagement.createGroupFailed'), 'error')
} finally { } finally {
creating.value = false creating.value = false
} }
@@ -334,7 +337,7 @@ const editGroup = (group) => {
// 更新分组 // 更新分组
const updateGroup = async () => { const updateGroup = async () => {
if (!editForm.value.name) { if (!editForm.value.name) {
showToast('请填写分组名称', 'error') showToast(t('groupManagement.fillGroupName'), 'error')
return return
} }
@@ -345,12 +348,12 @@ const updateGroup = async () => {
description: editForm.value.description description: editForm.value.description
}) })
showToast('分组更新成功', 'success') showToast(t('groupManagement.groupUpdated'), 'success')
cancelEdit() cancelEdit()
await loadGroups() await loadGroups()
emit('refresh') emit('refresh')
} catch (error) { } catch (error) {
showToast(error.response?.data?.error || '更新分组失败', 'error') showToast(error.response?.data?.error || t('groupManagement.updateGroupFailed'), 'error')
} finally { } finally {
updating.value = false updating.value = false
} }
@@ -370,21 +373,21 @@ const cancelEdit = () => {
// 删除分组 // 删除分组
const deleteGroup = async (group) => { const deleteGroup = async (group) => {
if (group.memberCount > 0) { if (group.memberCount > 0) {
showToast('分组内还有成员,无法删除', 'error') showToast(t('groupManagement.groupHasMembers'), 'error')
return return
} }
if (!confirm(`确定要删除分组 "${group.name}" 吗?`)) { if (!confirm(t('groupManagement.confirmDelete', { name: group.name }))) {
return return
} }
try { try {
await apiClient.delete(`/admin/account-groups/${group.id}`) await apiClient.delete(`/admin/account-groups/${group.id}`)
showToast('分组删除成功', 'success') showToast(t('groupManagement.groupDeleted'), 'success')
await loadGroups() await loadGroups()
emit('refresh') emit('refresh')
} catch (error) { } catch (error) {
showToast(error.response?.data?.error || '删除分组失败', 'error') showToast(error.response?.data?.error || t('groupManagement.deleteGroupFailed'), 'error')
} }
} }

View File

@@ -12,9 +12,9 @@
<i class="fas fa-link text-white" /> <i class="fas fa-link text-white" />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h4 class="mb-3 font-semibold text-blue-900 dark:text-blue-200">Claude 账户授权</h4> <h4 class="mb-3 font-semibold text-blue-900 dark:text-blue-200">{{ t('oauthFlow.claudeAccountAuth') }}</h4>
<p class="mb-4 text-sm text-blue-800 dark:text-blue-300"> <p class="mb-4 text-sm text-blue-800 dark:text-blue-300">
请按照以下步骤完成 Claude 账户的授权 {{ t('oauthFlow.claudeAuthDescription') }}
</p> </p>
<div class="space-y-4"> <div class="space-y-4">
@@ -30,7 +30,7 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-blue-900 dark:text-blue-200"> <p class="mb-2 font-medium text-blue-900 dark:text-blue-200">
点击下方按钮生成授权链接 {{ t('oauthFlow.step1Title') }}
</p> </p>
<button <button
v-if="!authUrl" v-if="!authUrl"
@@ -40,7 +40,7 @@
> >
<i v-if="!loading" class="fas fa-link mr-2" /> <i v-if="!loading" class="fas fa-link mr-2" />
<div v-else class="loading-spinner mr-2" /> <div v-else class="loading-spinner mr-2" />
{{ loading ? '生成中...' : '生成授权链接' }} {{ loading ? t('oauthFlow.generating') : t('oauthFlow.generateAuthLink') }}
</button> </button>
<div v-else class="space-y-3"> <div v-else class="space-y-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -52,7 +52,7 @@
/> />
<button <button
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600" class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
title="复制链接" :title="t('oauthFlow.copyLinkTooltip')"
@click="copyAuthUrl" @click="copyAuthUrl"
> >
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" /> <i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
@@ -62,7 +62,7 @@
class="text-xs text-blue-600 hover:text-blue-700" class="text-xs text-blue-600 hover:text-blue-700"
@click="regenerateAuthUrl" @click="regenerateAuthUrl"
> >
<i class="fas fa-sync-alt mr-1" />重新生成 <i class="fas fa-sync-alt mr-1" />{{ t('oauthFlow.regenerate') }}
</button> </button>
</div> </div>
</div> </div>
@@ -81,18 +81,18 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-blue-900 dark:text-blue-200"> <p class="mb-2 font-medium text-blue-900 dark:text-blue-200">
在浏览器中打开链接并完成授权 {{ t('oauthFlow.step2Title') }}
</p> </p>
<p class="mb-2 text-sm text-blue-700 dark:text-blue-300"> <p class="mb-2 text-sm text-blue-700 dark:text-blue-300">
请在新标签页中打开授权链接登录您的 Claude 账户并授权 {{ t('oauthFlow.step2Description') }}
</p> </p>
<div <div
class="rounded border border-yellow-300 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/30" class="rounded border border-yellow-300 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/30"
> >
<p class="text-xs text-yellow-800 dark:text-yellow-300"> <p class="text-xs text-yellow-800 dark:text-yellow-300">
<i class="fas fa-exclamation-triangle mr-1" /> <i class="fas fa-exclamation-triangle mr-1" />
<strong>注意</strong <strong>{{ t('oauthFlow.proxyNotice') }}</strong
>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面 >{{ t('oauthFlow.proxyNoticeText') }}
</p> </p>
</div> </div>
</div> </div>
@@ -111,29 +111,29 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-blue-900 dark:text-blue-200"> <p class="mb-2 font-medium text-blue-900 dark:text-blue-200">
输入 Authorization Code {{ t('oauthFlow.step3Title') }}
</p> </p>
<p class="mb-3 text-sm text-blue-700 dark:text-blue-300"> <p class="mb-3 text-sm text-blue-700 dark:text-blue-300">
授权完成后页面会显示一个 {{ t('oauthFlow.step3Description') }}
<strong>Authorization Code</strong>请将其复制并粘贴到下方输入框 <strong>{{ t('oauthFlow.authorizationCode') }}</strong>{{ t('oauthFlow.step3DescriptionMiddle') }}
</p> </p>
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<label <label
class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300" class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
> >
<i class="fas fa-key mr-2 text-blue-500" />Authorization Code <i class="fas fa-key mr-2 text-blue-500" />{{ t('oauthFlow.authorizationCode') }}
</label> </label>
<textarea <textarea
v-model="authCode" v-model="authCode"
class="form-input w-full resize-none font-mono text-sm" class="form-input w-full resize-none font-mono text-sm"
placeholder="粘贴从Claude页面获取的Authorization Code..." :placeholder="t('oauthFlow.authCodePlaceholder')"
rows="3" rows="3"
/> />
</div> </div>
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400"> <p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
<i class="fas fa-info-circle mr-1" /> <i class="fas fa-info-circle mr-1" />
请粘贴从Claude页面复制的Authorization Code {{ t('oauthFlow.authCodeHint') }}
</p> </p>
</div> </div>
</div> </div>
@@ -157,9 +157,9 @@
<i class="fas fa-robot text-white" /> <i class="fas fa-robot text-white" />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h4 class="mb-3 font-semibold text-green-900 dark:text-green-200">Gemini 账户授权</h4> <h4 class="mb-3 font-semibold text-green-900 dark:text-green-200">{{ t('oauthFlow.geminiAccountAuth') }}</h4>
<p class="mb-4 text-sm text-green-800 dark:text-green-300"> <p class="mb-4 text-sm text-green-800 dark:text-green-300">
请按照以下步骤完成 Gemini 账户的授权 {{ t('oauthFlow.geminiAuthDescription') }}
</p> </p>
<div class="space-y-4"> <div class="space-y-4">
@@ -175,7 +175,7 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-green-900 dark:text-green-200"> <p class="mb-2 font-medium text-green-900 dark:text-green-200">
点击下方按钮生成授权链接 {{ t('oauthFlow.step1Title') }}
</p> </p>
<button <button
v-if="!authUrl" v-if="!authUrl"
@@ -185,7 +185,7 @@
> >
<i v-if="!loading" class="fas fa-link mr-2" /> <i v-if="!loading" class="fas fa-link mr-2" />
<div v-else class="loading-spinner mr-2" /> <div v-else class="loading-spinner mr-2" />
{{ loading ? '生成中...' : '生成授权链接' }} {{ loading ? t('oauthFlow.generating') : t('oauthFlow.generateAuthLink') }}
</button> </button>
<div v-else class="space-y-3"> <div v-else class="space-y-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -197,7 +197,7 @@
/> />
<button <button
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600" class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
title="复制链接" :title="t('oauthFlow.copyLinkTooltip')"
@click="copyAuthUrl" @click="copyAuthUrl"
> >
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" /> <i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
@@ -207,7 +207,7 @@
class="text-xs text-green-600 hover:text-green-700" class="text-xs text-green-600 hover:text-green-700"
@click="regenerateAuthUrl" @click="regenerateAuthUrl"
> >
<i class="fas fa-sync-alt mr-1" />重新生成 <i class="fas fa-sync-alt mr-1" />{{ t('oauthFlow.regenerate') }}
</button> </button>
</div> </div>
</div> </div>
@@ -226,18 +226,18 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-green-900 dark:text-green-200"> <p class="mb-2 font-medium text-green-900 dark:text-green-200">
在浏览器中打开链接并完成授权 {{ t('oauthFlow.step2Title') }}
</p> </p>
<p class="mb-2 text-sm text-green-700 dark:text-green-300"> <p class="mb-2 text-sm text-green-700 dark:text-green-300">
请在新标签页中打开授权链接登录您的 Gemini 账户并授权 {{ t('oauthFlow.step2DescriptionGemini') }}
</p> </p>
<div <div
class="rounded border border-yellow-300 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/30" class="rounded border border-yellow-300 bg-yellow-50 p-3 dark:border-yellow-700 dark:bg-yellow-900/30"
> >
<p class="text-xs text-yellow-800 dark:text-yellow-300"> <p class="text-xs text-yellow-800 dark:text-yellow-300">
<i class="fas fa-exclamation-triangle mr-1" /> <i class="fas fa-exclamation-triangle mr-1" />
<strong>注意</strong <strong>{{ t('oauthFlow.proxyNotice') }}</strong
>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面 >{{ t('oauthFlow.proxyNoticeText') }}
</p> </p>
</div> </div>
</div> </div>
@@ -256,29 +256,29 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-green-900 dark:text-green-200"> <p class="mb-2 font-medium text-green-900 dark:text-green-200">
输入 Authorization Code {{ t('oauthFlow.step3Title') }}
</p> </p>
<p class="mb-3 text-sm text-green-700 dark:text-green-300"> <p class="mb-3 text-sm text-green-700 dark:text-green-300">
授权完成后页面会显示一个 Authorization Code请将其复制并粘贴到下方输入框 {{ t('oauthFlow.step3DescriptionGemini') }}
</p> </p>
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<label <label
class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300" class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
> >
<i class="fas fa-key mr-2 text-green-500" />Authorization Code <i class="fas fa-key mr-2 text-green-500" />{{ t('oauthFlow.authorizationCode') }}
</label> </label>
<textarea <textarea
v-model="authCode" v-model="authCode"
class="form-input w-full resize-none font-mono text-sm" class="form-input w-full resize-none font-mono text-sm"
placeholder="粘贴从Gemini页面获取的Authorization Code..." :placeholder="t('oauthFlow.authCodePlaceholderGemini')"
rows="3" rows="3"
/> />
</div> </div>
<div class="mt-2 space-y-1"> <div class="mt-2 space-y-1">
<p class="text-xs text-gray-600 dark:text-gray-400"> <p class="text-xs text-gray-600 dark:text-gray-400">
<i class="fas fa-check-circle mr-1 text-green-500" /> <i class="fas fa-check-circle mr-1 text-green-500" />
请粘贴从Gemini页面复制的Authorization Code {{ t('oauthFlow.authCodeHintGemini') }}
</p> </p>
</div> </div>
</div> </div>
@@ -303,9 +303,9 @@
<i class="fas fa-brain text-white" /> <i class="fas fa-brain text-white" />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<h4 class="mb-3 font-semibold text-orange-900 dark:text-orange-200">OpenAI 账户授权</h4> <h4 class="mb-3 font-semibold text-orange-900 dark:text-orange-200">{{ t('oauthFlow.openaiAccountAuth') }}</h4>
<p class="mb-4 text-sm text-orange-800 dark:text-orange-300"> <p class="mb-4 text-sm text-orange-800 dark:text-orange-300">
请按照以下步骤完成 OpenAI 账户的授权 {{ t('oauthFlow.openaiAuthDescription') }}
</p> </p>
<div class="space-y-4"> <div class="space-y-4">
@@ -321,7 +321,7 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-orange-900 dark:text-orange-200"> <p class="mb-2 font-medium text-orange-900 dark:text-orange-200">
点击下方按钮生成授权链接 {{ t('oauthFlow.step1Title') }}
</p> </p>
<button <button
v-if="!authUrl" v-if="!authUrl"
@@ -331,7 +331,7 @@
> >
<i v-if="!loading" class="fas fa-link mr-2" /> <i v-if="!loading" class="fas fa-link mr-2" />
<div v-else class="loading-spinner mr-2" /> <div v-else class="loading-spinner mr-2" />
{{ loading ? '生成中...' : '生成授权链接' }} {{ loading ? t('oauthFlow.generating') : t('oauthFlow.generateAuthLink') }}
</button> </button>
<div v-else class="space-y-3"> <div v-else class="space-y-3">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
@@ -343,7 +343,7 @@
/> />
<button <button
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600" class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600"
title="复制链接" :title="t('oauthFlow.copyLinkTooltip')"
@click="copyAuthUrl" @click="copyAuthUrl"
> >
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" /> <i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
@@ -353,7 +353,7 @@
class="text-xs text-orange-600 hover:text-orange-700" class="text-xs text-orange-600 hover:text-orange-700"
@click="regenerateAuthUrl" @click="regenerateAuthUrl"
> >
<i class="fas fa-sync-alt mr-1" />重新生成 <i class="fas fa-sync-alt mr-1" />{{ t('oauthFlow.regenerate') }}
</button> </button>
</div> </div>
</div> </div>
@@ -372,22 +372,22 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-orange-900 dark:text-orange-200"> <p class="mb-2 font-medium text-orange-900 dark:text-orange-200">
在浏览器中打开链接并完成授权 {{ t('oauthFlow.step2Title') }}
</p> </p>
<p class="mb-2 text-sm text-orange-700 dark:text-orange-300"> <p class="mb-2 text-sm text-orange-700 dark:text-orange-300">
请在新标签页中打开授权链接登录您的 OpenAI 账户并授权 {{ t('oauthFlow.step2DescriptionOpenAI') }}
</p> </p>
<div <div
class="mb-3 rounded border border-amber-300 bg-amber-50 p-3 dark:border-amber-700 dark:bg-amber-900/30" class="mb-3 rounded border border-amber-300 bg-amber-50 p-3 dark:border-amber-700 dark:bg-amber-900/30"
> >
<p class="text-xs text-amber-800 dark:text-amber-300"> <p class="text-xs text-amber-800 dark:text-amber-300">
<i class="fas fa-clock mr-1" /> <i class="fas fa-clock mr-1" />
<strong>重要提示</strong>授权后页面可能会加载较长时间请耐心等待 <strong>{{ t('oauthFlow.openaiImportantNote') }}</strong>{{ t('oauthFlow.openaiLoadingNote') }}
</p> </p>
<p class="mt-2 text-xs text-amber-700 dark:text-amber-400"> <p class="mt-2 text-xs text-amber-700 dark:text-amber-400">
当浏览器地址栏变为 {{ t('oauthFlow.openaiAddressNote') }}
<strong class="font-mono">http://localhost:1455/...</strong> <strong class="font-mono">http://localhost:1455/...</strong>
开头时表示授权已完成 {{ t('oauthFlow.openaiAddressNoteMiddle') }}
</p> </p>
</div> </div>
<div <div
@@ -395,8 +395,8 @@
> >
<p class="text-xs text-yellow-800 dark:text-yellow-300"> <p class="text-xs text-yellow-800 dark:text-yellow-300">
<i class="fas fa-exclamation-triangle mr-1" /> <i class="fas fa-exclamation-triangle mr-1" />
<strong>注意</strong <strong>{{ t('oauthFlow.proxyNotice') }}</strong
>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面 >{{ t('oauthFlow.proxyNoticeText') }}
</p> </p>
</div> </div>
</div> </div>
@@ -415,23 +415,23 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="mb-2 font-medium text-orange-900 dark:text-orange-200"> <p class="mb-2 font-medium text-orange-900 dark:text-orange-200">
输入授权链接或 Code {{ t('oauthFlow.step3TitleOpenAI') }}
</p> </p>
<p class="mb-3 text-sm text-orange-700 dark:text-orange-300"> <p class="mb-3 text-sm text-orange-700 dark:text-orange-300">
授权完成后当页面地址变为 {{ t('oauthFlow.step3DescriptionOpenAI') }}
<strong class="font-mono">http://localhost:1455/...</strong> 时: <strong class="font-mono">http://localhost:1455/...</strong> {{ t('oauthFlow.step3DescriptionOpenAIMiddle') }}
</p> </p>
<div class="space-y-3"> <div class="space-y-3">
<div> <div>
<label <label
class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300" class="mb-2 block text-sm font-semibold text-gray-700 dark:text-gray-300"
> >
<i class="fas fa-link mr-2 text-orange-500" />授权链接或 Code <i class="fas fa-link mr-2 text-orange-500" />{{ t('oauthFlow.authLinkOrCode') }}
</label> </label>
<textarea <textarea
v-model="authCode" v-model="authCode"
class="form-input w-full resize-none font-mono text-sm" class="form-input w-full resize-none font-mono text-sm"
placeholder="方式1复制完整的链接http://localhost:1455/auth/callback?code=...&#10;方式2仅复制 code 参数的值&#10;系统会自动识别并提取所需信息" :placeholder="t('oauthFlow.authCodePlaceholderOpenAI')"
rows="3" rows="3"
/> />
</div> </div>
@@ -440,16 +440,15 @@
> >
<p class="text-xs text-blue-700 dark:text-blue-300"> <p class="text-xs text-blue-700 dark:text-blue-300">
<i class="fas fa-lightbulb mr-1" /> <i class="fas fa-lightbulb mr-1" />
<strong>提示</strong>您可以直接复制整个链接或仅复制 code <strong>{{ t('oauthFlow.openaiTip') }}</strong>{{ t('oauthFlow.openaiTipText') }}
参数值系统会自动识别
</p> </p>
<p class="mt-1 text-xs text-blue-600 dark:text-blue-400"> <p class="mt-1 text-xs text-blue-600 dark:text-blue-400">
完整链接示例<span class="font-mono" {{ t('oauthFlow.openaiLinkExample') }}<span class="font-mono"
>http://localhost:1455/auth/callback?code=ac_4hm8...</span >http://localhost:1455/auth/callback?code=ac_4hm8...</span
> >
</p> </p>
<p class="text-xs text-blue-600"> <p class="text-xs text-blue-600">
Code 示例<span class="font-mono" {{ t('oauthFlow.openaiCodeExample') }}<span class="font-mono"
>ac_4hm8iqmx9A2fzMy_cwye7U3W7...</span >ac_4hm8iqmx9A2fzMy_cwye7U3W7...</span
> >
</p> </p>
@@ -470,7 +469,7 @@
type="button" type="button"
@click="$emit('back')" @click="$emit('back')"
> >
上一步 {{ t('oauthFlow.previousStep') }}
</button> </button>
<button <button
class="btn btn-primary flex-1 px-6 py-3 font-semibold" class="btn btn-primary flex-1 px-6 py-3 font-semibold"
@@ -479,7 +478,7 @@
@click="exchangeCode" @click="exchangeCode"
> >
<div v-if="exchanging" class="loading-spinner mr-2" /> <div v-if="exchanging" class="loading-spinner mr-2" />
{{ exchanging ? '验证中...' : '完成授权' }} {{ exchanging ? t('oauthFlow.verifying') : t('oauthFlow.completeAuth') }}
</button> </button>
</div> </div>
</div> </div>
@@ -487,9 +486,12 @@
<script setup> <script setup>
import { ref, computed, watch } from 'vue' import { ref, computed, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { showToast } from '@/utils/toast' import { showToast } from '@/utils/toast'
import { useAccountsStore } from '@/stores/accounts' import { useAccountsStore } from '@/stores/accounts'
const { t } = useI18n()
const props = defineProps({ const props = defineProps({
platform: { platform: {
type: String, type: String,
@@ -544,16 +546,16 @@ watch(authCode, (newValue) => {
if (code) { if (code) {
// 成功提取授权码 // 成功提取授权码
authCode.value = code authCode.value = code
showToast('成功提取授权码!', 'success') showToast(t('oauthFlow.successExtractCode'), 'success')
console.log('Successfully extracted authorization code from URL') console.log('Successfully extracted authorization code from URL')
} else { } else {
// URL 中没有 code 参数 // URL 中没有 code 参数
showToast('URL 中未找到授权码参数,请检查链接是否正确', 'error') showToast(t('oauthFlow.errorCodeNotFound'), 'error')
} }
} catch (error) { } catch (error) {
// URL 解析失败 // URL 解析失败
console.error('Failed to parse URL:', error) console.error('Failed to parse URL:', error)
showToast('链接格式错误,请检查是否为完整的 URL', 'error') showToast(t('oauthFlow.errorLinkFormat'), 'error')
} }
} else if (props.platform === 'gemini' || props.platform === 'openai') { } else if (props.platform === 'gemini' || props.platform === 'openai') {
// Gemini 和 OpenAI 平台可能使用不同的回调URL // Gemini 和 OpenAI 平台可能使用不同的回调URL
@@ -564,14 +566,14 @@ watch(authCode, (newValue) => {
if (code) { if (code) {
authCode.value = code authCode.value = code
showToast('成功提取授权码!', 'success') showToast(t('oauthFlow.successExtractCode'), 'success')
} }
} catch (error) { } catch (error) {
// 不是有效的URL保持原值 // 不是有效的URL保持原值
} }
} else { } else {
// 错误的 URL不是正确的 localhost 回调地址) // 错误的 URL不是正确的 localhost 回调地址)
showToast('请粘贴以 http://localhost:1455 或 http://localhost:45462 开头的链接', 'error') showToast(t('oauthFlow.errorWrongUrlFormat'), 'error')
} }
} }
// 如果不是 URL保持原值兼容直接输入授权码 // 如果不是 URL保持原值兼容直接输入授权码
@@ -607,7 +609,7 @@ const generateAuthUrl = async () => {
sessionId.value = result.sessionId sessionId.value = result.sessionId
} }
} catch (error) { } catch (error) {
showToast(error.message || '生成授权链接失败', 'error') showToast(error.message || t('oauthFlow.generateAuthFailed'), 'error')
} finally { } finally {
loading.value = false loading.value = false
} }
@@ -625,7 +627,7 @@ const copyAuthUrl = async () => {
try { try {
await navigator.clipboard.writeText(authUrl.value) await navigator.clipboard.writeText(authUrl.value)
copied.value = true copied.value = true
showToast('链接已复制', 'success') showToast(t('oauthFlow.linkCopied'), 'success')
setTimeout(() => { setTimeout(() => {
copied.value = false copied.value = false
}, 2000) }, 2000)
@@ -638,7 +640,7 @@ const copyAuthUrl = async () => {
document.execCommand('copy') document.execCommand('copy')
document.body.removeChild(input) document.body.removeChild(input)
copied.value = true copied.value = true
showToast('链接已复制', 'success') showToast(t('oauthFlow.linkCopied'), 'success')
setTimeout(() => { setTimeout(() => {
copied.value = false copied.value = false
}, 2000) }, 2000)
@@ -695,7 +697,7 @@ const exchangeCode = async () => {
emit('success', tokenInfo) emit('success', tokenInfo)
} catch (error) { } catch (error) {
showToast(error.message || '授权失败,请检查授权码是否正确', 'error') showToast(error.message || t('oauthFlow.authFailed'), 'error')
} finally { } finally {
exchanging.value = false exchanging.value = false
} }

View File

@@ -1,14 +1,14 @@
<template> <template>
<div class="space-y-4"> <div class="space-y-4">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300">代理设置 (可选)</h4> <h4 class="text-sm font-semibold text-gray-700 dark:text-gray-300">{{ t('proxyConfig.title') }}</h4>
<label class="flex cursor-pointer items-center"> <label class="flex cursor-pointer items-center">
<input <input
v-model="proxy.enabled" v-model="proxy.enabled"
class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-blue-500" class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-blue-500"
type="checkbox" type="checkbox"
/> />
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">启用代理</span> <span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ t('proxyConfig.enableProxy') }}</span>
</label> </label>
</div> </div>
@@ -22,10 +22,10 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<p class="text-sm text-gray-700 dark:text-gray-300"> <p class="text-sm text-gray-700 dark:text-gray-300">
配置代理以访问受限的网络资源支持 SOCKS5 HTTP 代理 {{ t('proxyConfig.configDescription') }}
</p> </p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400"> <p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
请确保代理服务器稳定可用否则会影响账户的正常使用 {{ t('proxyConfig.stabilityNotice') }}
</p> </p>
</div> </div>
</div> </div>
@@ -71,7 +71,7 @@
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" <label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>代理类型</label >{{ t('proxyConfig.proxyType') }}</label
> >
<select <select
v-model="proxy.type" v-model="proxy.type"
@@ -86,23 +86,23 @@
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" <label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>主机地址</label >{{ t('proxyConfig.hostAddress') }}</label
> >
<input <input
v-model="proxy.host" v-model="proxy.host"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400" class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="例如: 192.168.1.100" :placeholder="t('proxyConfig.hostPlaceholder')"
type="text" type="text"
/> />
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" <label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>端口</label >{{ t('proxyConfig.port') }}</label
> >
<input <input
v-model="proxy.port" v-model="proxy.port"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400" class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="例如: 1080" :placeholder="t('proxyConfig.portPlaceholder')"
type="number" type="number"
/> />
</div> </div>
@@ -120,31 +120,31 @@
class="ml-2 cursor-pointer text-sm text-gray-700 dark:text-gray-300" class="ml-2 cursor-pointer text-sm text-gray-700 dark:text-gray-300"
for="proxyAuth" for="proxyAuth"
> >
需要身份验证 {{ t('proxyConfig.needsAuth') }}
</label> </label>
</div> </div>
<div v-if="showAuth" class="grid grid-cols-2 gap-4"> <div v-if="showAuth" class="grid grid-cols-2 gap-4">
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" <label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>用户名</label >{{ t('proxyConfig.username') }}</label
> >
<input <input
v-model="proxy.username" v-model="proxy.username"
class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400" class="form-input w-full border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="代理用户名" :placeholder="t('proxyConfig.usernamePlaceholder')"
type="text" type="text"
/> />
</div> </div>
<div> <div>
<label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300" <label class="mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300"
>密码</label >{{ t('proxyConfig.password') }}</label
> >
<div class="relative"> <div class="relative">
<input <input
v-model="proxy.password" v-model="proxy.password"
class="form-input w-full border-gray-300 pr-10 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400" class="form-input w-full border-gray-300 pr-10 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:placeholder-gray-400"
placeholder="代理密码" :placeholder="t('proxyConfig.passwordPlaceholder')"
:type="showPassword ? 'text' : 'password'" :type="showPassword ? 'text' : 'password'"
/> />
<button <button
@@ -164,8 +164,8 @@
> >
<p class="text-xs text-blue-700 dark:text-blue-300"> <p class="text-xs text-blue-700 dark:text-blue-300">
<i class="fas fa-info-circle mr-1" /> <i class="fas fa-info-circle mr-1" />
<strong>提示</strong <strong>{{ t('proxyConfig.tip') }}</strong
>代理设置将用于所有与此账户相关的API请求请确保代理服务器支持HTTPS流量转发 >{{ t('proxyConfig.apiRequestNotice') }}
</p> </p>
</div> </div>
</div> </div>
@@ -174,6 +174,9 @@
<script setup> <script setup>
import { ref, watch, onUnmounted } from 'vue' import { ref, watch, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {

View File

@@ -1706,5 +1706,139 @@ export default {
// Claude subscription type display // Claude subscription type display
claudeMaxDisplay: 'Claude Max', claudeMaxDisplay: 'Claude Max',
claudeProDisplay: 'Claude Pro' claudeProDisplay: 'Claude Pro'
},
// OAuth Flow Component
oauthFlow: {
// Platform titles
claudeAccountAuth: 'Claude Account Authorization',
geminiAccountAuth: 'Gemini Account Authorization',
openaiAccountAuth: 'OpenAI Account Authorization',
// Flow descriptions
claudeAuthDescription: 'Please follow these steps to complete Claude account authorization:',
geminiAuthDescription: 'Please follow these steps to complete Gemini account authorization:',
openaiAuthDescription: 'Please follow these steps to complete OpenAI account authorization:',
// Step titles
step1Title: 'Click the button below to generate authorization link',
step2Title: 'Open the link in browser and complete authorization',
step3Title: 'Input Authorization Code',
step3TitleOpenAI: 'Input authorization link or Code',
// Step descriptions
step2Description: 'Please open the authorization link in a new tab, log in to your Claude account and authorize.',
step2DescriptionGemini: 'Please open the authorization link in a new tab, log in to your Gemini account and authorize.',
step2DescriptionOpenAI: 'Please open the authorization link in a new tab, log in to your OpenAI account and authorize.',
step3Description: 'After authorization is complete, the page will display a',
step3DescriptionMiddle: ', please copy and paste it into the input box below:',
step3DescriptionGemini: 'After authorization is complete, the page will display an Authorization Code, please copy and paste it into the input box below:',
step3DescriptionOpenAI: 'After authorization is complete, when the page address changes to',
step3DescriptionOpenAIMiddle: ':',
// Button text
generating: 'Generating...',
generateAuthLink: 'Generate Authorization Link',
regenerate: 'Regenerate',
previousStep: 'Previous Step',
completeAuth: 'Complete Authorization',
verifying: 'Verifying...',
// Placeholders
authCodePlaceholder: 'Paste the Authorization Code obtained from Claude page...',
authCodePlaceholderGemini: 'Paste the Authorization Code obtained from Gemini page...',
authCodePlaceholderOpenAI: 'Method 1: Copy the complete link (http://localhost:1455/auth/callback?code=...)\nMethod 2: Copy only the code parameter value\nThe system will automatically recognize and extract the required information',
// Labels
authorizationCode: 'Authorization Code',
authLinkOrCode: 'Authorization Link or Code',
// Hints
copyLinkTooltip: 'Copy Link',
authCodeHint: 'Please paste the Authorization Code copied from Claude page',
authCodeHintGemini: 'Please paste the Authorization Code copied from Gemini page',
// Notices
proxyNotice: 'Note:',
proxyNoticeText: 'If you have set up a proxy, please ensure the browser also uses the same proxy to access the authorization page.',
// OpenAI specific hints
openaiImportantNote: 'Important Notice:',
openaiLoadingNote: 'The page may take a long time to load after authorization, please wait patiently.',
openaiAddressNote: 'When the browser address bar changes to',
openaiAddressNoteMiddle: 'at the beginning, it means authorization is complete.',
openaiTip: 'Tip:',
openaiTipText: 'You can directly copy the entire link or just copy the code parameter value, the system will automatically recognize.',
openaiLinkExample: '• Complete link example:',
openaiCodeExample: '• Code only example:',
// Success and error messages
successExtractCode: 'Successfully extracted authorization code!',
errorCodeNotFound: 'Authorization code parameter not found in URL, please check if the link is correct',
errorLinkFormat: 'Link format error, please check if it is a complete URL',
errorWrongUrlFormat: 'Please paste links starting with http://localhost:1455 or http://localhost:45462',
linkCopied: 'Link copied',
authFailed: 'Authorization failed, please check if the authorization code is correct',
generateAuthFailed: 'Failed to generate authorization link'
},
// Group Management Modal
groupManagement: {
title: 'Account Group Management',
createNewGroup: 'Create New Group',
createGroup: 'Create New Group',
groupNameRequired: 'Group Name *',
groupNamePlaceholder: 'Enter group name',
platformTypeRequired: 'Platform Type *',
descriptionOptional: 'Description (Optional)',
descriptionPlaceholder: 'Group description...',
creating: 'Creating...',
create: 'Create',
cancel: 'Cancel',
loading: 'Loading...',
noGroups: 'No groups',
noDescription: 'No description',
membersCount: ' members',
edit: 'Edit',
delete: 'Delete',
editGroup: 'Edit Group',
platformTypeLabel: 'Platform Type',
cannotModify: '(Cannot modify)',
updating: 'Updating...',
update: 'Update',
// Toast messages
loadGroupsFailed: 'Failed to load group list',
fillRequiredFields: 'Please fill in required fields',
groupCreated: 'Group created successfully',
createGroupFailed: 'Failed to create group',
fillGroupName: 'Please fill in group name',
groupUpdated: 'Group updated successfully',
updateGroupFailed: 'Failed to update group',
groupHasMembers: 'Group has members, cannot delete',
confirmDelete: 'Are you sure you want to delete group "{name}"?',
groupDeleted: 'Group deleted successfully',
deleteGroupFailed: 'Failed to delete group'
},
// Proxy Configuration
proxyConfig: {
title: 'Proxy Settings (Optional)',
enableProxy: 'Enable Proxy',
configDescription: 'Configure proxy to access restricted network resources. Supports SOCKS5 and HTTP proxy.',
stabilityNotice: 'Please ensure the proxy server is stable and available, otherwise it will affect normal account usage.',
proxyType: 'Proxy Type',
hostAddress: 'Host Address',
hostPlaceholder: 'e.g., 192.168.1.100',
port: 'Port',
portPlaceholder: 'e.g., 1080',
needsAuth: 'Requires Authentication',
username: 'Username',
usernamePlaceholder: 'Proxy username',
password: 'Password',
passwordPlaceholder: 'Proxy password',
tip: 'Tip:',
apiRequestNotice: 'Proxy settings will be used for all API requests related to this account. Please ensure the proxy server supports HTTPS traffic forwarding.'
} }
} }

View File

@@ -1706,5 +1706,139 @@ export default {
// Claude 订阅类型显示 // Claude 订阅类型显示
claudeMaxDisplay: 'Claude Max', claudeMaxDisplay: 'Claude Max',
claudeProDisplay: 'Claude Pro' claudeProDisplay: 'Claude Pro'
},
// OAuth Flow Component
oauthFlow: {
// 平台标题
claudeAccountAuth: 'Claude 账户授权',
geminiAccountAuth: 'Gemini 账户授权',
openaiAccountAuth: 'OpenAI 账户授权',
// 流程说明
claudeAuthDescription: '请按照以下步骤完成 Claude 账户的授权:',
geminiAuthDescription: '请按照以下步骤完成 Gemini 账户的授权:',
openaiAuthDescription: '请按照以下步骤完成 OpenAI 账户的授权:',
// 步骤标题
step1Title: '点击下方按钮生成授权链接',
step2Title: '在浏览器中打开链接并完成授权',
step3Title: '输入 Authorization Code',
step3TitleOpenAI: '输入授权链接或 Code',
// 步骤描述
step2Description: '请在新标签页中打开授权链接,登录您的 Claude 账户并授权。',
step2DescriptionGemini: '请在新标签页中打开授权链接,登录您的 Gemini 账户并授权。',
step2DescriptionOpenAI: '请在新标签页中打开授权链接,登录您的 OpenAI 账户并授权。',
step3Description: '授权完成后,页面会显示一个',
step3DescriptionMiddle: ',请将其复制并粘贴到下方输入框:',
step3DescriptionGemini: '授权完成后,页面会显示一个 Authorization Code请将其复制并粘贴到下方输入框',
step3DescriptionOpenAI: '授权完成后,当页面地址变为',
step3DescriptionOpenAIMiddle: '时:',
// 按钮文本
generating: '生成中...',
generateAuthLink: '生成授权链接',
regenerate: '重新生成',
previousStep: '上一步',
completeAuth: '完成授权',
verifying: '验证中...',
// 占位符
authCodePlaceholder: '粘贴从Claude页面获取的Authorization Code...',
authCodePlaceholderGemini: '粘贴从Gemini页面获取的Authorization Code...',
authCodePlaceholderOpenAI: '方式1复制完整的链接http://localhost:1455/auth/callback?code=...)\n方式2仅复制 code 参数的值\n系统会自动识别并提取所需信息',
// 标签
authorizationCode: 'Authorization Code',
authLinkOrCode: '授权链接或 Code',
// 提示信息
copyLinkTooltip: '复制链接',
authCodeHint: '请粘贴从Claude页面复制的Authorization Code',
authCodeHintGemini: '请粘贴从Gemini页面复制的Authorization Code',
// 注意事项
proxyNotice: '注意:',
proxyNoticeText: '如果您设置了代理,请确保浏览器也使用相同的代理访问授权页面。',
// OpenAI 特有提示
openaiImportantNote: '重要提示:',
openaiLoadingNote: '授权后页面可能会加载较长时间,请耐心等待。',
openaiAddressNote: '当浏览器地址栏变为',
openaiAddressNoteMiddle: '开头时,表示授权已完成。',
openaiTip: '提示:',
openaiTipText: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别。',
openaiLinkExample: '• 完整链接示例:',
openaiCodeExample: '• 仅 Code 示例:',
// 成功和错误消息
successExtractCode: '成功提取授权码!',
errorCodeNotFound: 'URL 中未找到授权码参数,请检查链接是否正确',
errorLinkFormat: '链接格式错误,请检查是否为完整的 URL',
errorWrongUrlFormat: '请粘贴以 http://localhost:1455 或 http://localhost:45462 开头的链接',
linkCopied: '链接已复制',
authFailed: '授权失败,请检查授权码是否正确',
generateAuthFailed: '生成授权链接失败'
},
// Group Management Modal
groupManagement: {
title: '账户分组管理',
createNewGroup: '创建新分组',
createGroup: '创建新分组',
groupNameRequired: '分组名称 *',
groupNamePlaceholder: '输入分组名称',
platformTypeRequired: '平台类型 *',
descriptionOptional: '描述 (可选)',
descriptionPlaceholder: '分组描述...',
creating: '创建中...',
create: '创建',
cancel: '取消',
loading: '加载中...',
noGroups: '暂无分组',
noDescription: '暂无描述',
membersCount: ' 个成员',
edit: '编辑',
delete: '删除',
editGroup: '编辑分组',
platformTypeLabel: '平台类型',
cannotModify: '(不可修改)',
updating: '更新中...',
update: '更新',
// Toast messages
loadGroupsFailed: '加载分组列表失败',
fillRequiredFields: '请填写必填项',
groupCreated: '分组创建成功',
createGroupFailed: '创建分组失败',
fillGroupName: '请填写分组名称',
groupUpdated: '分组更新成功',
updateGroupFailed: '更新分组失败',
groupHasMembers: '分组内还有成员,无法删除',
confirmDelete: '确定要删除分组 "{name}" 吗?',
groupDeleted: '分组删除成功',
deleteGroupFailed: '删除分组失败'
},
// Proxy Configuration
proxyConfig: {
title: '代理设置 (可选)',
enableProxy: '启用代理',
configDescription: '配置代理以访问受限的网络资源。支持 SOCKS5 和 HTTP 代理。',
stabilityNotice: '请确保代理服务器稳定可用,否则会影响账户的正常使用。',
proxyType: '代理类型',
hostAddress: '主机地址',
hostPlaceholder: '例如: 192.168.1.100',
port: '端口',
portPlaceholder: '例如: 1080',
needsAuth: '需要身份验证',
username: '用户名',
usernamePlaceholder: '代理用户名',
password: '密码',
passwordPlaceholder: '代理密码',
tip: '提示:',
apiRequestNotice: '代理设置将用于所有与此账户相关的API请求。请确保代理服务器支持HTTPS流量转发。'
} }
} }

View File

@@ -1706,5 +1706,139 @@ export default {
// Claude 訂閱類型顯示 // Claude 訂閱類型顯示
claudeMaxDisplay: 'Claude Max', claudeMaxDisplay: 'Claude Max',
claudeProDisplay: 'Claude Pro' claudeProDisplay: 'Claude Pro'
},
// OAuth Flow Component
oauthFlow: {
// 平台標題
claudeAccountAuth: 'Claude 帳戶授權',
geminiAccountAuth: 'Gemini 帳戶授權',
openaiAccountAuth: 'OpenAI 帳戶授權',
// 流程說明
claudeAuthDescription: '請按照以下步驟完成 Claude 帳戶的授權:',
geminiAuthDescription: '請按照以下步驟完成 Gemini 帳戶的授權:',
openaiAuthDescription: '請按照以下步驟完成 OpenAI 帳戶的授權:',
// 步驟標題
step1Title: '點擊下方按銮產生授權連結',
step2Title: '在瀏覽器中打開連結並完成授權',
step3Title: '輸入 Authorization Code',
step3TitleOpenAI: '輸入授權連結或 Code',
// 步驟描述
step2Description: '請在新分頁中打開授權連結,登入您的 Claude 帳戶並授權。',
step2DescriptionGemini: '請在新分頁中打開授權連結,登入您的 Gemini 帳戶並授權。',
step2DescriptionOpenAI: '請在新分頁中打開授權連結,登入您的 OpenAI 帳戶並授權。',
step3Description: '授權完成後,頁面會顯示一個',
step3DescriptionMiddle: ',請將其複製並貼上到下方輸入框:',
step3DescriptionGemini: '授權完成後,頁面會顯示一個 Authorization Code請將其複製並貼上到下方輸入框',
step3DescriptionOpenAI: '授權完成後,當頁面地址變為',
step3DescriptionOpenAIMiddle: '時:',
// 按銮文字
generating: '產生中...',
generateAuthLink: '產生授權連結',
regenerate: '重新產生',
previousStep: '上一步',
completeAuth: '完成授權',
verifying: '驗證中...',
// 占位符
authCodePlaceholder: '貼上Claude頁面獲取的Authorization Code...',
authCodePlaceholderGemini: '貼上Gemini頁面獲取的Authorization Code...',
authCodePlaceholderOpenAI: '方式1複製完整的連結http://localhost:1455/auth/callback?code=...)\n方式2僅複製 code 參數的值\n系統會自動識別並提取所需資訊',
// 標籤
authorizationCode: 'Authorization Code',
authLinkOrCode: '授權連結或 Code',
// 提示資訊
copyLinkTooltip: '複製連結',
authCodeHint: '請貼上Claude頁面複製的Authorization Code',
authCodeHintGemini: '請貼上Gemini頁面複製的Authorization Code',
// 注意事項
proxyNotice: '注意:',
proxyNoticeText: '如果您設定了代理,請確保瀏覽器也使用相同的代理訪問授權頁面。',
// OpenAI 特有提示
openaiImportantNote: '重要提示:',
openaiLoadingNote: '授權後頁面可能會加載較長時間,請耐心等待。',
openaiAddressNote: '當瀏覽器地址欄變為',
openaiAddressNoteMiddle: '開頭時,表示授權已完成。',
openaiTip: '提示:',
openaiTipText: '您可以直接複製整個連結或僅複製 code 參數值,系統會自動識別。',
openaiLinkExample: '• 完整連結示例:',
openaiCodeExample: '• 僅 Code 示例:',
// 成功和錯誤消息
successExtractCode: '成功提取授權碼!',
errorCodeNotFound: 'URL 中未找到授權碼參數,請檢查連結是否正確',
errorLinkFormat: '連結格式錯誤,請檢查是否為完整的 URL',
errorWrongUrlFormat: '請貼上以 http://localhost:1455 或 http://localhost:45462 開頭的連結',
linkCopied: '連結已複製',
authFailed: '授權失敗,請檢查授權碼是否正確',
generateAuthFailed: '產生授權連結失敗'
},
// Group Management Modal
groupManagement: {
title: '帳戶分組管理',
createNewGroup: '創建新分組',
createGroup: '創建新分組',
groupNameRequired: '分組名稱 *',
groupNamePlaceholder: '輸入分組名稱',
platformTypeRequired: '平台類型 *',
descriptionOptional: '描述 (可選)',
descriptionPlaceholder: '分組描述...',
creating: '創建中...',
create: '創建',
cancel: '取消',
loading: '加載中...',
noGroups: '暫無分組',
noDescription: '暫無描述',
membersCount: ' 個成員',
edit: '編輯',
delete: '刪除',
editGroup: '編輯分組',
platformTypeLabel: '平台類型',
cannotModify: '(不可修改)',
updating: '更新中...',
update: '更新',
// Toast messages
loadGroupsFailed: '加載分組列表失敗',
fillRequiredFields: '請填寫必填項',
groupCreated: '分組創建成功',
createGroupFailed: '創建分組失敗',
fillGroupName: '請填寫分組名稱',
groupUpdated: '分組更新成功',
updateGroupFailed: '更新分組失敗',
groupHasMembers: '分組內還有成員,無法刪除',
confirmDelete: '確定要刪除分組 "{name}" 嗎?',
groupDeleted: '分組刪除成功',
deleteGroupFailed: '刪除分組失敗'
},
// Proxy Configuration
proxyConfig: {
title: '代理設置 (可選)',
enableProxy: '啟用代理',
configDescription: '配置代理以訪問受限的網路資源。支持 SOCKS5 和 HTTP 代理。',
stabilityNotice: '請確保代理伺服器穩定可用,否則會影響帳戶的正常使用。',
proxyType: '代理類型',
hostAddress: '主機地址',
hostPlaceholder: '例如: 192.168.1.100',
port: '端口',
portPlaceholder: '例如: 1080',
needsAuth: '需要身份驗證',
username: '用戶名',
usernamePlaceholder: '代理用戶名',
password: '密碼',
passwordPlaceholder: '代理密碼',
tip: '提示:',
apiRequestNotice: '代理設置將用於所有與此帳戶相關的API請求。請確保代理伺服器支持HTTPS流量轉發。'
} }
} }