refactor: standardize code formatting and linting configuration

- Replace .eslintrc.js with .eslintrc.cjs for better ES module compatibility
- Add .prettierrc configuration for consistent code formatting
- Update package.json with new lint and format scripts
- Add nodemon.json for development hot reloading configuration
- Standardize code formatting across all JavaScript and Vue files
- Update web admin SPA with improved linting rules and formatting
- Add prettier configuration to web admin SPA

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
千羽
2025-08-07 18:19:31 +09:00
parent 4a0eba117c
commit 8a74bf5afe
124 changed files with 20878 additions and 18757 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,158 +1,118 @@
<template>
<Teleport to="body">
<div
v-if="show"
class="fixed inset-0 modal z-50 flex items-center justify-center p-3 sm:p-4"
>
<div class="modal-content w-full max-w-4xl p-4 sm:p-6 md:p-8 mx-auto max-h-[90vh] overflow-y-auto custom-scrollbar">
<div class="flex items-center justify-between mb-4 sm:mb-6">
<div v-if="show" class="modal fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-4">
<div
class="modal-content custom-scrollbar mx-auto max-h-[90vh] w-full max-w-4xl overflow-y-auto p-4 sm:p-6 md:p-8"
>
<div class="mb-4 flex items-center justify-between sm:mb-6">
<div class="flex items-center gap-2 sm:gap-3">
<div class="w-8 h-8 sm:w-10 sm:h-10 bg-gradient-to-br from-purple-500 to-purple-600 rounded-lg sm:rounded-xl flex items-center justify-center">
<i class="fas fa-layer-group text-white text-sm sm:text-base" />
<div
class="flex h-8 w-8 items-center justify-center rounded-lg bg-gradient-to-br from-purple-500 to-purple-600 sm:h-10 sm:w-10 sm:rounded-xl"
>
<i class="fas fa-layer-group text-sm text-white sm:text-base" />
</div>
<h3 class="text-lg sm:text-xl font-bold text-gray-900">
账户分组管理
</h3>
<h3 class="text-lg font-bold text-gray-900 sm:text-xl">账户分组管理</h3>
</div>
<button
class="text-gray-400 hover:text-gray-600 transition-colors p-1"
<button
class="p-1 text-gray-400 transition-colors hover:text-gray-600"
@click="$emit('close')"
>
<i class="fas fa-times text-lg sm:text-xl" />
</button>
</div>
<!-- 添加分组按钮 -->
<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" />
创建新分组
</button>
</div>
<!-- 创建分组表单 -->
<div
v-if="showCreateForm"
class="mb-6 p-4 bg-blue-50 rounded-lg border border-blue-200"
>
<h4 class="text-lg font-semibold text-gray-900 mb-4">
创建新分组
</h4>
<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>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">分组名称 *</label>
<label class="mb-2 block text-sm font-semibold text-gray-700">分组名称 *</label>
<input
v-model="createForm.name"
type="text"
class="form-input w-full"
placeholder="输入分组名称"
>
type="text"
/>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">平台类型 *</label>
<label class="mb-2 block text-sm font-semibold text-gray-700">平台类型 *</label>
<div class="flex gap-4">
<label class="flex items-center cursor-pointer">
<input
v-model="createForm.platform"
type="radio"
value="claude"
class="mr-2"
>
<label class="flex cursor-pointer items-center">
<input v-model="createForm.platform" class="mr-2" type="radio" value="claude" />
<span class="text-sm text-gray-700">Claude</span>
</label>
<label class="flex items-center cursor-pointer">
<input
v-model="createForm.platform"
type="radio"
value="gemini"
class="mr-2"
>
<label class="flex cursor-pointer items-center">
<input v-model="createForm.platform" class="mr-2" type="radio" value="gemini" />
<span class="text-sm text-gray-700">Gemini</span>
</label>
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">描述 (可选)</label>
<label class="mb-2 block text-sm font-semibold text-gray-700">描述 (可选)</label>
<textarea
v-model="createForm.description"
rows="2"
class="form-input w-full resize-none"
placeholder="分组描述..."
rows="2"
/>
</div>
<div class="flex gap-3">
<button
class="btn btn-primary px-4 py-2"
:disabled="!createForm.name || !createForm.platform || creating"
@click="createGroup"
>
<div
v-if="creating"
class="loading-spinner mr-2"
/>
<div v-if="creating" class="loading-spinner mr-2" />
{{ creating ? '创建中...' : '创建' }}
</button>
<button
class="btn btn-secondary px-4 py-2"
@click="cancelCreate"
>
取消
</button>
<button class="btn btn-secondary px-4 py-2" @click="cancelCreate">取消</button>
</div>
</div>
</div>
<!-- 分组列表 -->
<div class="space-y-4">
<div
v-if="loading"
class="text-center py-8"
>
<div v-if="loading" class="py-8 text-center">
<div class="loading-spinner-lg mx-auto mb-4" />
<p class="text-gray-500">
加载中...
</p>
<p class="text-gray-500">加载中...</p>
</div>
<div
v-else-if="groups.length === 0"
class="text-center py-8 bg-gray-50 rounded-lg"
>
<i class="fas fa-layer-group text-4xl text-gray-300 mb-4" />
<p class="text-gray-500">
暂无分组
</p>
<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" />
<p class="text-gray-500">暂无分组</p>
</div>
<div
v-else
class="grid gap-4 grid-cols-1 md:grid-cols-2"
>
<div v-else class="grid grid-cols-1 gap-4 md:grid-cols-2">
<div
v-for="group in groups"
:key="group.id"
class="bg-white rounded-lg border p-4 hover:shadow-md transition-shadow"
class="rounded-lg border bg-white p-4 transition-shadow hover:shadow-md"
>
<div class="flex items-start justify-between mb-3">
<div class="mb-3 flex items-start justify-between">
<div class="flex-1">
<h4 class="font-semibold text-gray-900">
{{ group.name }}
</h4>
<p class="text-sm text-gray-500 mt-1">
<p class="mt-1 text-sm text-gray-500">
{{ group.description || '暂无描述' }}
</p>
</div>
<div class="flex items-center gap-2 ml-4">
<div class="ml-4 flex items-center gap-2">
<span
:class="[
'px-2 py-1 text-xs font-medium rounded-full',
group.platform === 'claude'
'rounded-full px-2 py-1 text-xs font-medium',
group.platform === 'claude'
? 'bg-purple-100 text-purple-700'
: 'bg-blue-100 text-blue-700'
]"
@@ -161,7 +121,7 @@
</span>
</div>
</div>
<div class="flex items-center justify-between text-sm text-gray-600">
<div class="flex items-center gap-4">
<span>
@@ -175,16 +135,16 @@
</div>
<div class="flex items-center gap-2">
<button
class="text-blue-600 hover:text-blue-800 transition-colors"
class="text-blue-600 transition-colors hover:text-blue-800"
title="编辑"
@click="editGroup(group)"
>
<i class="fas fa-edit" />
</button>
<button
class="text-red-600 hover:text-red-800 transition-colors"
title="删除"
class="text-red-600 transition-colors hover:text-red-800"
:disabled="group.memberCount > 0"
title="删除"
@click="deleteGroup(group)"
>
<i class="fas fa-trash" />
@@ -196,72 +156,59 @@
</div>
</div>
</div>
<!-- 编辑分组模态框 -->
<div
v-if="showEditForm"
class="fixed inset-0 modal z-60 flex items-center justify-center p-3 sm:p-4"
class="modal z-60 fixed inset-0 flex items-center justify-center p-3 sm:p-4"
>
<div class="modal-content w-full max-w-lg p-4 sm:p-6">
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-bold text-gray-900">
编辑分组
</h3>
<button
class="text-gray-400 hover:text-gray-600 transition-colors"
@click="cancelEdit"
>
<div class="mb-4 flex items-center justify-between">
<h3 class="text-lg font-bold text-gray-900">编辑分组</h3>
<button class="text-gray-400 transition-colors hover:text-gray-600" @click="cancelEdit">
<i class="fas fa-times" />
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">分组名称 *</label>
<label class="mb-2 block text-sm font-semibold text-gray-700">分组名称 *</label>
<input
v-model="editForm.name"
type="text"
class="form-input w-full"
placeholder="输入分组名称"
>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">平台类型</label>
<div class="px-3 py-2 bg-gray-100 rounded-lg text-sm text-gray-600">
{{ editForm.platform === 'claude' ? 'Claude' : 'Gemini' }}
<span class="text-xs text-gray-500 ml-2">(不可修改)</span>
</div>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">描述 (可选)</label>
<textarea
v-model="editForm.description"
rows="2"
class="form-input w-full resize-none"
placeholder="分组描述..."
type="text"
/>
</div>
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700">平台类型</label>
<div class="rounded-lg bg-gray-100 px-3 py-2 text-sm text-gray-600">
{{ editForm.platform === 'claude' ? 'Claude' : 'Gemini' }}
<span class="ml-2 text-xs text-gray-500">(不可修改)</span>
</div>
</div>
<div>
<label class="mb-2 block text-sm font-semibold text-gray-700">描述 (可选)</label>
<textarea
v-model="editForm.description"
class="form-input w-full resize-none"
placeholder="分组描述..."
rows="2"
/>
</div>
<div class="flex gap-3 pt-4">
<button
class="btn btn-primary px-4 py-2 flex-1"
class="btn btn-primary flex-1 px-4 py-2"
:disabled="!editForm.name || updating"
@click="updateGroup"
>
<div
v-if="updating"
class="loading-spinner mr-2"
/>
<div v-if="updating" class="loading-spinner mr-2" />
{{ updating ? '更新中...' : '更新' }}
</button>
<button
class="btn btn-secondary px-4 py-2 flex-1"
@click="cancelEdit"
>
取消
</button>
<button class="btn btn-secondary flex-1 px-4 py-2" @click="cancelEdit">取消</button>
</div>
</div>
</div>
@@ -325,7 +272,7 @@ const createGroup = async () => {
showToast('请填写必填项', 'error')
return
}
creating.value = true
try {
await apiClient.post('/admin/account-groups', {
@@ -333,7 +280,7 @@ const createGroup = async () => {
platform: createForm.value.platform,
description: createForm.value.description
})
showToast('分组创建成功', 'success')
cancelCreate()
await loadGroups()
@@ -372,14 +319,14 @@ const updateGroup = async () => {
showToast('请填写分组名称', 'error')
return
}
updating.value = true
try {
await apiClient.put(`/admin/account-groups/${editingGroup.value.id}`, {
name: editForm.value.name,
description: editForm.value.description
})
showToast('分组更新成功', 'success')
cancelEdit()
await loadGroups()
@@ -408,11 +355,11 @@ const deleteGroup = async (group) => {
showToast('分组内还有成员,无法删除', 'error')
return
}
if (!confirm(`确定要删除分组 "${group.name}" 吗?`)) {
return
}
try {
await apiClient.delete(`/admin/account-groups/${group.id}`)
showToast('分组删除成功', 'success')
@@ -427,4 +374,4 @@ const deleteGroup = async (group) => {
onMounted(() => {
loadGroups()
})
</script>
</script>

View File

@@ -2,66 +2,55 @@
<div class="space-y-6">
<!-- Claude OAuth流程 -->
<div v-if="platform === 'claude'">
<div class="bg-blue-50 p-6 rounded-lg border border-blue-200">
<div class="rounded-lg border border-blue-200 bg-blue-50 p-6">
<div class="flex items-start gap-4">
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-blue-500"
>
<i class="fas fa-link text-white" />
</div>
<div class="flex-1">
<h4 class="font-semibold text-blue-900 mb-3">
Claude 账户授权
</h4>
<p class="text-sm text-blue-800 mb-4">
请按照以下步骤完成 Claude 账户的授权
</p>
<h4 class="mb-3 font-semibold text-blue-900">Claude 账户授权</h4>
<p class="mb-4 text-sm text-blue-800">请按照以下步骤完成 Claude 账户授权</p>
<div class="space-y-4">
<!-- 步骤1: 生成授权链接 -->
<div class="bg-white/80 rounded-lg p-4 border border-blue-300">
<div class="rounded-lg border border-blue-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-bold text-white"
>
1
</div>
<div class="flex-1">
<p class="font-medium text-blue-900 mb-2">
点击下方按钮生成授权链接
</p>
<button
<p class="mb-2 font-medium text-blue-900">点击下方按钮生成授权链接</p>
<button
v-if="!authUrl"
:disabled="loading"
class="btn btn-primary px-4 py-2 text-sm"
:disabled="loading"
@click="generateAuthUrl"
>
<i
v-if="!loading"
class="fas fa-link mr-2"
/>
<div
v-else
class="loading-spinner mr-2"
/>
<i v-if="!loading" class="fas fa-link mr-2" />
<div v-else class="loading-spinner mr-2" />
{{ loading ? '生成中...' : '生成授权链接' }}
</button>
<div
v-else
class="space-y-3"
>
<div v-else class="space-y-3">
<div class="flex items-center gap-2">
<input
type="text"
:value="authUrl"
<input
class="form-input flex-1 bg-gray-50 font-mono text-xs"
readonly
class="form-input flex-1 text-xs font-mono bg-gray-50"
>
<button
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
type="text"
:value="authUrl"
/>
<button
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200"
title="复制链接"
@click="copyAuthUrl"
>
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
</button>
</div>
<button
<button
class="text-xs text-blue-600 hover:text-blue-700"
@click="regenerateAuthUrl"
>
@@ -71,56 +60,58 @@
</div>
</div>
</div>
<!-- 步骤2: 访问链接并授权 -->
<div class="bg-white/80 rounded-lg p-4 border border-blue-300">
<div class="rounded-lg border border-blue-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-bold text-white"
>
2
</div>
<div class="flex-1">
<p class="font-medium text-blue-900 mb-2">
在浏览器中打开链接并完成授权
</p>
<p class="text-sm text-blue-700 mb-2">
<p class="mb-2 font-medium text-blue-900">在浏览器中打开链接并完成授权</p>
<p class="mb-2 text-sm text-blue-700">
请在新标签页中打开授权链接登录您的 Claude 账户并授权
</p>
<div class="bg-yellow-50 p-3 rounded border border-yellow-300">
<div class="rounded border border-yellow-300 bg-yellow-50 p-3">
<p class="text-xs text-yellow-800">
<i class="fas fa-exclamation-triangle mr-1" />
<strong>注意</strong>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面
<strong>注意</strong
>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面
</p>
</div>
</div>
</div>
</div>
<!-- 步骤3: 输入授权码 -->
<div class="bg-white/80 rounded-lg p-4 border border-blue-300">
<div class="rounded-lg border border-blue-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-blue-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-blue-600 text-xs font-bold text-white"
>
3
</div>
<div class="flex-1">
<p class="font-medium text-blue-900 mb-2">
输入 Authorization Code
</p>
<p class="text-sm text-blue-700 mb-3">
授权完成后页面会显示一个 <strong>Authorization Code</strong>请将其复制并粘贴到下方输入框
<p class="mb-2 font-medium text-blue-900">输入 Authorization Code</p>
<p class="mb-3 text-sm text-blue-700">
授权完成后页面会显示一个
<strong>Authorization Code</strong>请将其复制并粘贴到下方输入框
</p>
<div class="space-y-3">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-key text-blue-500 mr-2" />Authorization Code
<label class="mb-2 block text-sm font-semibold text-gray-700">
<i class="fas fa-key mr-2 text-blue-500" />Authorization Code
</label>
<textarea
v-model="authCode"
rows="3"
<textarea
v-model="authCode"
class="form-input w-full resize-none font-mono text-sm"
placeholder="粘贴从Claude页面获取的Authorization Code..."
rows="3"
/>
</div>
<p class="text-xs text-gray-500 mt-2">
<p class="mt-2 text-xs text-gray-500">
<i class="fas fa-info-circle mr-1" />
请粘贴从Claude页面复制的Authorization Code
</p>
@@ -133,69 +124,58 @@
</div>
</div>
</div>
<!-- Gemini OAuth流程 -->
<div v-else-if="platform === 'gemini'">
<div class="bg-green-50 p-6 rounded-lg border border-green-200">
<div class="rounded-lg border border-green-200 bg-green-50 p-6">
<div class="flex items-start gap-4">
<div class="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center flex-shrink-0">
<div
class="flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg bg-green-500"
>
<i class="fas fa-robot text-white" />
</div>
<div class="flex-1">
<h4 class="font-semibold text-green-900 mb-3">
Gemini 账户授权
</h4>
<p class="text-sm text-green-800 mb-4">
请按照以下步骤完成 Gemini 账户的授权
</p>
<h4 class="mb-3 font-semibold text-green-900">Gemini 账户授权</h4>
<p class="mb-4 text-sm text-green-800">请按照以下步骤完成 Gemini 账户授权</p>
<div class="space-y-4">
<!-- 步骤1: 生成授权链接 -->
<div class="bg-white/80 rounded-lg p-4 border border-green-300">
<div class="rounded-lg border border-green-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-green-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-600 text-xs font-bold text-white"
>
1
</div>
<div class="flex-1">
<p class="font-medium text-green-900 mb-2">
点击下方按钮生成授权链接
</p>
<button
<p class="mb-2 font-medium text-green-900">点击下方按钮生成授权链接</p>
<button
v-if="!authUrl"
:disabled="loading"
class="btn btn-primary px-4 py-2 text-sm"
:disabled="loading"
@click="generateAuthUrl"
>
<i
v-if="!loading"
class="fas fa-link mr-2"
/>
<div
v-else
class="loading-spinner mr-2"
/>
<i v-if="!loading" class="fas fa-link mr-2" />
<div v-else class="loading-spinner mr-2" />
{{ loading ? '生成中...' : '生成授权链接' }}
</button>
<div
v-else
class="space-y-3"
>
<div v-else class="space-y-3">
<div class="flex items-center gap-2">
<input
type="text"
:value="authUrl"
<input
class="form-input flex-1 bg-gray-50 font-mono text-xs"
readonly
class="form-input flex-1 text-xs font-mono bg-gray-50"
>
<button
class="px-3 py-2 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
type="text"
:value="authUrl"
/>
<button
class="rounded-lg bg-gray-100 px-3 py-2 transition-colors hover:bg-gray-200"
title="复制链接"
@click="copyAuthUrl"
>
<i :class="copied ? 'fas fa-check text-green-500' : 'fas fa-copy'" />
</button>
</div>
<button
<button
class="text-xs text-green-600 hover:text-green-700"
@click="regenerateAuthUrl"
>
@@ -205,59 +185,60 @@
</div>
</div>
</div>
<!-- 步骤2: 操作说明 -->
<div class="bg-white/80 rounded-lg p-4 border border-green-300">
<div class="rounded-lg border border-green-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-green-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-600 text-xs font-bold text-white"
>
2
</div>
<div class="flex-1">
<p class="font-medium text-blue-900 mb-2">
在浏览器中打开链接并完成授权
</p>
<p class="text-sm text-blue-700 mb-2">
<p class="mb-2 font-medium text-blue-900">在浏览器中打开链接并完成授权</p>
<p class="mb-2 text-sm text-blue-700">
请在新标签页中打开授权链接登录您的 Gemini 账户并授权
</p>
<div class="bg-yellow-50 p-3 rounded border border-yellow-300">
<div class="rounded border border-yellow-300 bg-yellow-50 p-3">
<p class="text-xs text-yellow-800">
<i class="fas fa-exclamation-triangle mr-1" />
<strong>注意</strong>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面
<strong>注意</strong
>如果您设置了代理请确保浏览器也使用相同的代理访问授权页面
</p>
</div>
</div>
</div>
</div>
<!-- 步骤3: 输入授权码 -->
<div class="bg-white/80 rounded-lg p-4 border border-green-300">
<div class="rounded-lg border border-green-300 bg-white/80 p-4">
<div class="flex items-start gap-3">
<div class="w-6 h-6 bg-green-600 text-white rounded-full flex items-center justify-center text-xs font-bold flex-shrink-0">
<div
class="flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-green-600 text-xs font-bold text-white"
>
3
</div>
<div class="flex-1">
<p class="font-medium text-green-900 mb-2">
输入 Authorization Code
</p>
<p class="text-sm text-green-700 mb-3">
<p class="mb-2 font-medium text-green-900">输入 Authorization Code</p>
<p class="mb-3 text-sm text-green-700">
授权完成后页面会显示一个 Authorization Code请将其复制并粘贴到下方输入框
</p>
<div class="space-y-3">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-key text-green-500 mr-2" />Authorization Code
<label class="mb-2 block text-sm font-semibold text-gray-700">
<i class="fas fa-key mr-2 text-green-500" />Authorization Code
</label>
<textarea
v-model="authCode"
rows="3"
<textarea
v-model="authCode"
class="form-input w-full resize-none font-mono text-sm"
placeholder="粘贴从Gemini页面获取的Authorization Code..."
rows="3"
/>
</div>
<div class="mt-2 space-y-1">
<p class="text-xs text-gray-600">
<i class="fas fa-check-circle text-green-500 mr-1" />
请粘贴从Gemini页面复制的Authorization Code
<i class="fas fa-check-circle mr-1 text-green-500" />
请粘贴从Gemini页面复制的Authorization Code
</p>
</div>
</div>
@@ -269,25 +250,22 @@
</div>
</div>
</div>
<div class="flex gap-3 pt-4">
<button
type="button"
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
<button
class="flex-1 rounded-xl bg-gray-100 px-6 py-3 font-semibold text-gray-700 transition-colors hover:bg-gray-200"
type="button"
@click="$emit('back')"
>
上一步
</button>
<button
type="button"
<button
class="btn btn-primary flex-1 px-6 py-3 font-semibold"
:disabled="!canExchange || exchanging"
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
type="button"
@click="exchangeCode"
>
<div
v-if="exchanging"
class="loading-spinner mr-2"
/>
<div v-if="exchanging" class="loading-spinner mr-2" />
{{ exchanging ? '验证中...' : '完成授权' }}
</button>
</div>
@@ -330,15 +308,15 @@ const canExchange = computed(() => {
// 监听授权码输入自动提取URL中的code参数
watch(authCode, (newValue) => {
if (!newValue || typeof newValue !== 'string') return
const trimmedValue = newValue.trim()
// 如果内容为空,不处理
if (!trimmedValue) return
// 检查是否是 URL 格式(包含 http:// 或 https://
const isUrl = trimmedValue.startsWith('http://') || trimmedValue.startsWith('https://')
// 如果是 URL 格式
if (isUrl) {
// 检查是否是正确的 localhost:45462 开头的 URL
@@ -346,7 +324,7 @@ watch(authCode, (newValue) => {
try {
const url = new URL(trimmedValue)
const code = url.searchParams.get('code')
if (code) {
// 成功提取授权码
authCode.value = code
@@ -367,7 +345,7 @@ watch(authCode, (newValue) => {
try {
const url = new URL(trimmedValue)
const code = url.searchParams.get('code')
if (code) {
authCode.value = code
showToast('成功提取授权码!', 'success')
@@ -387,16 +365,18 @@ watch(authCode, (newValue) => {
const generateAuthUrl = async () => {
loading.value = true
try {
const proxyConfig = props.proxy?.enabled ? {
proxy: {
type: props.proxy.type,
host: props.proxy.host,
port: parseInt(props.proxy.port),
username: props.proxy.username || null,
password: props.proxy.password || null
}
} : {}
const proxyConfig = props.proxy?.enabled
? {
proxy: {
type: props.proxy.type,
host: props.proxy.host,
port: parseInt(props.proxy.port),
username: props.proxy.username || null,
password: props.proxy.password || null
}
}
: {}
if (props.platform === 'claude') {
const result = await accountsStore.generateClaudeAuthUrl(proxyConfig)
authUrl.value = result.authUrl
@@ -448,11 +428,11 @@ const copyAuthUrl = async () => {
// 交换授权码
const exchangeCode = async () => {
if (!canExchange.value) return
exchanging.value = true
try {
let data = {}
if (props.platform === 'claude') {
// Claude使用sessionId和callbackUrl即授权码
data = {
@@ -466,7 +446,7 @@ const exchangeCode = async () => {
sessionId: sessionId.value
}
}
// 添加代理配置(如果启用)
if (props.proxy?.enabled) {
data.proxy = {
@@ -477,14 +457,14 @@ const exchangeCode = async () => {
password: props.proxy.password || null
}
}
let tokenInfo
if (props.platform === 'claude') {
tokenInfo = await accountsStore.exchangeClaudeCode(data)
} else if (props.platform === 'gemini') {
tokenInfo = await accountsStore.exchangeGeminiCode(data)
}
emit('success', tokenInfo)
} catch (error) {
showToast(error.message || '授权失败,请检查授权码是否正确', 'error')
@@ -492,4 +472,4 @@ const exchangeCode = async () => {
exchanging.value = false
}
}
</script>
</script>

View File

@@ -1,117 +1,97 @@
<template>
<div class="space-y-4">
<div class="flex items-center justify-between">
<h4 class="text-sm font-semibold text-gray-700">
代理设置 (可选)
</h4>
<label class="flex items-center cursor-pointer">
<input
v-model="proxy.enabled"
<h4 class="text-sm font-semibold text-gray-700">代理设置 (可选)</h4>
<label class="flex cursor-pointer items-center">
<input
v-model="proxy.enabled"
class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-blue-500"
type="checkbox"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
>
/>
<span class="ml-2 text-sm text-gray-700">启用代理</span>
</label>
</div>
<div
v-if="proxy.enabled"
class="bg-gray-50 p-4 rounded-lg border border-gray-200 space-y-4"
>
<div class="flex items-start gap-3 mb-3">
<div class="w-8 h-8 bg-gray-500 rounded-lg flex items-center justify-center flex-shrink-0">
<i class="fas fa-server text-white text-sm" />
<div v-if="proxy.enabled" class="space-y-4 rounded-lg border border-gray-200 bg-gray-50 p-4">
<div class="mb-3 flex items-start gap-3">
<div class="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-lg bg-gray-500">
<i class="fas fa-server text-sm text-white" />
</div>
<div class="flex-1">
<p class="text-sm text-gray-700">
配置代理以访问受限的网络资源支持 SOCKS5 HTTP 代理
</p>
<p class="text-xs text-gray-500 mt-1">
<p class="mt-1 text-xs text-gray-500">
请确保代理服务器稳定可用否则会影响账户的正常使用
</p>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">代理类型</label>
<select
v-model="proxy.type"
class="form-input w-full"
>
<option value="socks5">
SOCKS5
</option>
<option value="http">
HTTP
</option>
<option value="https">
HTTPS
</option>
<label class="mb-2 block text-sm font-medium text-gray-700">代理类型</label>
<select v-model="proxy.type" class="form-input w-full">
<option value="socks5">SOCKS5</option>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">主机地址</label>
<input
v-model="proxy.host"
type="text"
placeholder="例如: 192.168.1.100"
<label class="mb-2 block text-sm font-medium text-gray-700">主机地址</label>
<input
v-model="proxy.host"
class="form-input w-full"
>
placeholder="例如: 192.168.1.100"
type="text"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">端口</label>
<input
v-model="proxy.port"
type="number"
placeholder="例如: 1080"
<label class="mb-2 block text-sm font-medium text-gray-700">端口</label>
<input
v-model="proxy.port"
class="form-input w-full"
>
placeholder="例如: 1080"
type="number"
/>
</div>
</div>
<div class="space-y-4">
<div class="flex items-center">
<input
id="proxyAuth"
<input
id="proxyAuth"
v-model="showAuth"
class="h-4 w-4 rounded border-gray-300 bg-gray-100 text-blue-600 focus:ring-blue-500"
type="checkbox"
class="w-4 h-4 text-blue-600 bg-gray-100 border-gray-300 rounded focus:ring-blue-500"
>
<label
for="proxyAuth"
class="ml-2 text-sm text-gray-700 cursor-pointer"
>
/>
<label class="ml-2 cursor-pointer text-sm text-gray-700" for="proxyAuth">
需要身份验证
</label>
</div>
<div
v-if="showAuth"
class="grid grid-cols-2 gap-4"
>
<div v-if="showAuth" class="grid grid-cols-2 gap-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">用户名</label>
<input
v-model="proxy.username"
type="text"
placeholder="代理用户名"
<label class="mb-2 block text-sm font-medium text-gray-700">用户名</label>
<input
v-model="proxy.username"
class="form-input w-full"
>
placeholder="代理用户名"
type="text"
/>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">密码</label>
<label class="mb-2 block text-sm font-medium text-gray-700">密码</label>
<div class="relative">
<input
v-model="proxy.password"
:type="showPassword ? 'text' : 'password'"
placeholder="代理密码"
<input
v-model="proxy.password"
class="form-input w-full pr-10"
>
<button
placeholder="代理密码"
:type="showPassword ? 'text' : 'password'"
/>
<button
class="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-400 hover:text-gray-600"
type="button"
class="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 hover:text-gray-600"
@click="showPassword = !showPassword"
>
<i :class="showPassword ? 'fas fa-eye-slash' : 'fas fa-eye'" />
@@ -120,11 +100,12 @@
</div>
</div>
</div>
<div class="bg-blue-50 p-3 rounded-lg border border-blue-200">
<div class="rounded-lg border border-blue-200 bg-blue-50 p-3">
<p class="text-xs text-blue-700">
<i class="fas fa-info-circle mr-1" />
<strong>提示</strong>代理设置将用于所有与此账户相关的API请求请确保代理服务器支持HTTPS流量转发
<strong>提示</strong
>代理设置将用于所有与此账户相关的API请求请确保代理服务器支持HTTPS流量转发
</p>
</div>
</div>
@@ -132,7 +113,7 @@
</template>
<script setup>
import { ref, computed, watch, onUnmounted } from 'vue'
import { ref, watch, onUnmounted } from 'vue'
const props = defineProps({
modelValue: {
@@ -158,38 +139,60 @@ const showAuth = ref(!!(proxy.value.username || proxy.value.password))
const showPassword = ref(false)
// 监听modelValue变化只在真正需要更新时才更新
watch(() => props.modelValue, (newVal) => {
// 只有当值真正不同时才更新,避免循环
if (JSON.stringify(newVal) !== JSON.stringify(proxy.value)) {
proxy.value = { ...newVal }
showAuth.value = !!(newVal.username || newVal.password)
}
}, { deep: true })
watch(
() => props.modelValue,
(newVal) => {
// 只有当值真正不同时才更新,避免循环
if (JSON.stringify(newVal) !== JSON.stringify(proxy.value)) {
proxy.value = { ...newVal }
showAuth.value = !!(newVal.username || newVal.password)
}
},
{ deep: true }
)
// 监听各个字段单独变化,而不是整个对象
watch(() => proxy.value.enabled, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.enabled,
() => {
emitUpdate()
}
)
watch(() => proxy.value.type, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.type,
() => {
emitUpdate()
}
)
watch(() => proxy.value.host, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.host,
() => {
emitUpdate()
}
)
watch(() => proxy.value.port, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.port,
() => {
emitUpdate()
}
)
watch(() => proxy.value.username, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.username,
() => {
emitUpdate()
}
)
watch(() => proxy.value.password, (newVal) => {
emitUpdate()
})
watch(
() => proxy.value.password,
() => {
emitUpdate()
}
)
// 监听认证开关
watch(showAuth, (newVal) => {
@@ -207,17 +210,17 @@ function emitUpdate() {
if (updateTimer) {
clearTimeout(updateTimer)
}
// 设置新的定时器,延迟发送更新
updateTimer = setTimeout(() => {
const data = { ...proxy.value }
// 如果不需要认证,清空用户名密码
if (!showAuth.value) {
data.username = ''
data.password = ''
}
emit('update:modelValue', data)
}, 100) // 100ms 延迟
}
@@ -228,4 +231,4 @@ onUnmounted(() => {
clearTimeout(updateTimer)
}
})
</script>
</script>