mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
- 新增API Key并发控制功能 - 创建API Key时可设置并发限制(0为不限制) - 在认证中间件中实现并发检查 - 使用Redis原子操作确保计数准确 - 添加自动清理机制处理异常情况 - 新增API Key编辑功能 - 支持修改Token限制和并发限制 - 前端添加编辑按钮和模态框 - 后端限制只能修改指定字段 - 其他改进 - 添加test-concurrency.js测试脚本 - 添加详细的功能说明文档 - 所有代码通过ESLint检查 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
2394 lines
169 KiB
HTML
2394 lines
169 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Claude Relay Service - 管理后台</title>
|
||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.js"></script>
|
||
<!-- Element Plus -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/element-plus/2.4.4/index.min.css">
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-plus/2.4.4/index.full.min.js"></script>
|
||
<!-- Element Plus 中文语言包 -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/element-plus/2.4.4/locale/zh-cn.min.js"></script>
|
||
<!-- Font Awesome -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||
<link rel="stylesheet" href="/web/style.css">
|
||
</head>
|
||
<body>
|
||
<div id="app" v-cloak>
|
||
<!-- 登录界面 -->
|
||
<div v-if="!isLoggedIn" class="flex items-center justify-center min-h-screen p-6">
|
||
<div class="glass-strong rounded-3xl p-10 w-full max-w-md shadow-2xl">
|
||
<div class="text-center mb-8">
|
||
<div class="w-20 h-20 mx-auto mb-6 bg-gradient-to-br from-blue-500/20 to-purple-500/20 border border-gray-300/30 rounded-2xl flex items-center justify-center backdrop-blur-sm">
|
||
<i class="fas fa-cloud text-3xl text-gray-700"></i>
|
||
</div>
|
||
<h1 class="text-3xl font-bold text-white mb-2 header-title">Claude Relay Service</h1>
|
||
<p class="text-white/80 text-lg">管理后台</p>
|
||
</div>
|
||
|
||
<form @submit.prevent="login" class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-white mb-3">用户名</label>
|
||
<input
|
||
v-model="loginForm.username"
|
||
type="text"
|
||
required
|
||
class="form-input w-full"
|
||
placeholder="请输入用户名"
|
||
>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-white mb-3">密码</label>
|
||
<input
|
||
v-model="loginForm.password"
|
||
type="password"
|
||
required
|
||
class="form-input w-full"
|
||
placeholder="请输入密码"
|
||
>
|
||
</div>
|
||
|
||
<button
|
||
type="submit"
|
||
:disabled="loginLoading"
|
||
class="btn btn-primary w-full py-4 px-6 text-lg font-semibold"
|
||
>
|
||
<i v-if="!loginLoading" class="fas fa-sign-in-alt mr-2"></i>
|
||
<div v-if="loginLoading" class="loading-spinner mr-2"></div>
|
||
{{ loginLoading ? '登录中...' : '登录' }}
|
||
</button>
|
||
</form>
|
||
|
||
<div v-if="loginError" class="mt-6 p-4 bg-red-500/20 border border-red-500/30 rounded-xl text-red-200 text-sm text-center backdrop-blur-sm">
|
||
<i class="fas fa-exclamation-triangle mr-2"></i>{{ loginError }}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 管理界面 -->
|
||
<div v-if="isLoggedIn" class="min-h-screen p-6">
|
||
<!-- 顶部导航 -->
|
||
<div class="glass-strong rounded-3xl p-6 mb-8 shadow-xl">
|
||
<div class="flex flex-col md:flex-row justify-between items-center gap-4">
|
||
<div class="flex items-center gap-4">
|
||
<div class="w-12 h-12 bg-gradient-to-br from-blue-500/20 to-purple-500/20 border border-gray-300/30 rounded-xl flex items-center justify-center backdrop-blur-sm flex-shrink-0">
|
||
<i class="fas fa-cloud text-xl text-gray-700"></i>
|
||
</div>
|
||
<div class="flex flex-col justify-center min-h-[48px]">
|
||
<h1 class="text-2xl font-bold text-white header-title leading-tight">Claude Relay Service</h1>
|
||
<p class="text-gray-600 text-sm leading-tight mt-0.5">管理后台</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
@click="logout"
|
||
class="btn btn-danger px-6 py-3 flex items-center gap-2"
|
||
>
|
||
<i class="fas fa-sign-out-alt"></i>退出登录
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 主内容区域 -->
|
||
<div class="glass-strong rounded-3xl p-6 shadow-xl">
|
||
<!-- 标签栏 -->
|
||
<div class="flex flex-wrap gap-2 mb-8 bg-white/10 rounded-2xl p-2 backdrop-blur-sm">
|
||
<button
|
||
v-for="tab in tabs"
|
||
:key="tab.key"
|
||
@click="activeTab = tab.key"
|
||
:class="['tab-btn flex-1 py-3 px-6 text-sm font-semibold transition-all duration-300',
|
||
activeTab === tab.key ? 'active' : 'text-gray-700 hover:bg-white/10 hover:text-gray-900']"
|
||
>
|
||
<i :class="tab.icon + ' mr-2'"></i>{{ tab.name }}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 仪表板 -->
|
||
<div v-if="activeTab === 'dashboard'" class="tab-content">
|
||
<!-- 主要统计 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 mb-8">
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">总API Keys</p>
|
||
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.totalApiKeys }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">活跃: {{ dashboardData.activeApiKeys || 0 }}</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-blue-500 to-blue-600">
|
||
<i class="fas fa-key"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">Claude账户</p>
|
||
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.totalAccounts }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">活跃: {{ dashboardData.activeAccounts || 0 }}</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-green-500 to-green-600">
|
||
<i class="fas fa-user-circle"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">今日请求</p>
|
||
<p class="text-3xl font-bold text-gray-900">{{ dashboardData.todayRequests }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">总请求: {{ formatNumber(dashboardData.totalRequests || 0) }}</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-purple-500 to-purple-600">
|
||
<i class="fas fa-chart-line"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">系统状态</p>
|
||
<p class="text-3xl font-bold text-green-600">{{ dashboardData.systemStatus }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">运行时间: {{ formatUptime(dashboardData.uptime) }}</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-yellow-500 to-orange-500">
|
||
<i class="fas fa-heartbeat"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Token统计和性能指标 -->
|
||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 mb-8">
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex-1">
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">今日Token</p>
|
||
<div class="flex items-baseline gap-2 mb-2">
|
||
<p class="text-3xl font-bold text-blue-600">{{ formatNumber((dashboardData.todayInputTokens || 0) + (dashboardData.todayOutputTokens || 0) + (dashboardData.todayCacheCreateTokens || 0) + (dashboardData.todayCacheReadTokens || 0)) }}</p>
|
||
<span class="text-sm text-green-600 font-medium">/ {{ costsData.todayCosts.formatted.totalCost }}</span>
|
||
</div>
|
||
<div class="text-xs text-gray-500">
|
||
<div class="flex justify-between items-center flex-wrap gap-x-4">
|
||
<span>输入: <span class="font-medium">{{ formatNumber(dashboardData.todayInputTokens || 0) }}</span></span>
|
||
<span>输出: <span class="font-medium">{{ formatNumber(dashboardData.todayOutputTokens || 0) }}</span></span>
|
||
<span v-if="(dashboardData.todayCacheCreateTokens || 0) > 0" class="text-purple-600">缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheCreateTokens || 0) }}</span></span>
|
||
<span v-if="(dashboardData.todayCacheReadTokens || 0) > 0" class="text-purple-600">缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.todayCacheReadTokens || 0) }}</span></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-indigo-500 to-indigo-600">
|
||
<i class="fas fa-coins"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div class="flex-1">
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">总Token消耗</p>
|
||
<div class="flex items-baseline gap-2 mb-2">
|
||
<p class="text-3xl font-bold text-emerald-600">{{ formatNumber((dashboardData.totalInputTokens || 0) + (dashboardData.totalOutputTokens || 0) + (dashboardData.totalCacheCreateTokens || 0) + (dashboardData.totalCacheReadTokens || 0)) }}</p>
|
||
<span class="text-sm text-green-600 font-medium">/ {{ costsData.totalCosts.formatted.totalCost }}</span>
|
||
</div>
|
||
<div class="text-xs text-gray-500">
|
||
<div class="flex justify-between items-center flex-wrap gap-x-4">
|
||
<span>输入: <span class="font-medium">{{ formatNumber(dashboardData.totalInputTokens || 0) }}</span></span>
|
||
<span>输出: <span class="font-medium">{{ formatNumber(dashboardData.totalOutputTokens || 0) }}</span></span>
|
||
<span v-if="(dashboardData.totalCacheCreateTokens || 0) > 0" class="text-purple-600">缓存创建: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheCreateTokens || 0) }}</span></span>
|
||
<span v-if="(dashboardData.totalCacheReadTokens || 0) > 0" class="text-purple-600">缓存读取: <span class="font-medium">{{ formatNumber(dashboardData.totalCacheReadTokens || 0) }}</span></span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-emerald-500 to-emerald-600">
|
||
<i class="fas fa-database"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">平均RPM</p>
|
||
<p class="text-3xl font-bold text-orange-600">{{ dashboardData.systemRPM || 0 }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">每分钟请求数</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-orange-500 to-orange-600">
|
||
<i class="fas fa-tachometer-alt"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="stat-card">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<p class="text-sm font-semibold text-gray-600 mb-1">平均TPM</p>
|
||
<p class="text-3xl font-bold text-rose-600">{{ dashboardData.systemTPM || 0 }}</p>
|
||
<p class="text-xs text-gray-500 mt-1">每分钟Token数</p>
|
||
</div>
|
||
<div class="stat-icon bg-gradient-to-br from-rose-500 to-rose-600">
|
||
<i class="fas fa-rocket"></i>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模型消费统计 -->
|
||
<div class="mb-8">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<h3 class="text-xl font-bold text-gray-900">模型使用分布与Token使用趋势</h3>
|
||
<div class="flex gap-2 items-center">
|
||
<!-- 快捷日期选择 -->
|
||
<div class="flex gap-1 bg-gray-100 rounded-lg p-1">
|
||
<button
|
||
v-for="option in dateFilter.presetOptions"
|
||
:key="option.value"
|
||
@click="setDateFilterPreset(option.value)"
|
||
:class="[
|
||
'px-3 py-1 rounded-md text-sm font-medium transition-colors',
|
||
dateFilter.preset === option.value && dateFilter.type === 'preset'
|
||
? 'bg-white text-blue-600 shadow-sm'
|
||
: 'text-gray-600 hover:text-gray-900'
|
||
]"
|
||
>
|
||
{{ option.label }}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Element Plus 日期范围选择器 -->
|
||
<el-date-picker
|
||
v-model="dateFilter.customRange"
|
||
type="datetimerange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
@change="onCustomDateRangeChange"
|
||
:disabled-date="disabledDate"
|
||
size="default"
|
||
style="width: 350px;"
|
||
class="custom-date-picker"
|
||
></el-date-picker>
|
||
|
||
<button @click="refreshChartsData()" class="btn btn-primary px-4 py-2 flex items-center gap-2">
|
||
<i class="fas fa-sync-alt"></i>刷新
|
||
</button>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||
<!-- 饼图 -->
|
||
<div class="card p-6">
|
||
<h4 class="text-lg font-semibold text-gray-800 mb-4">Token使用分布</h4>
|
||
<div class="relative" style="height: 300px;">
|
||
<canvas id="modelUsageChart"></canvas>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 详细数据表格 -->
|
||
<div class="card p-6">
|
||
<h4 class="text-lg font-semibold text-gray-800 mb-4">详细统计数据</h4>
|
||
<div v-if="dashboardModelStats.length === 0" class="text-center py-8">
|
||
<p class="text-gray-500">暂无模型使用数据</p>
|
||
</div>
|
||
<div v-else class="overflow-auto max-h-[300px]">
|
||
<table class="min-w-full">
|
||
<thead class="bg-gray-50 sticky top-0">
|
||
<tr>
|
||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-700">模型</th>
|
||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">请求数</th>
|
||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">总Token</th>
|
||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">费用</th>
|
||
<th class="px-4 py-2 text-right text-xs font-medium text-gray-700">占比</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-200">
|
||
<tr v-for="stat in dashboardModelStats" :key="stat.model" class="hover:bg-gray-50">
|
||
<td class="px-4 py-2 text-sm text-gray-900">{{ stat.model }}</td>
|
||
<td class="px-4 py-2 text-sm text-gray-600 text-right">{{ formatNumber(stat.requests) }}</td>
|
||
<td class="px-4 py-2 text-sm text-gray-600 text-right">{{ formatNumber(stat.allTokens) }}</td>
|
||
<td class="px-4 py-2 text-sm text-green-600 text-right font-medium">{{ stat.formatted ? stat.formatted.total : '$0.000000' }}</td>
|
||
<td class="px-4 py-2 text-sm font-medium text-right">
|
||
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
|
||
{{ calculatePercentage(stat.allTokens, dashboardModelStats) }}%
|
||
</span>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Token使用趋势图 -->
|
||
<div class="mb-8">
|
||
<div class="card p-6">
|
||
<div style="height: 300px;">
|
||
<canvas id="usageTrendChart"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API Keys 管理 -->
|
||
<div v-if="activeTab === 'apiKeys'" class="tab-content">
|
||
<div class="card p-6">
|
||
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
|
||
<div>
|
||
<h3 class="text-xl font-bold text-gray-900 mb-2">API Keys 管理</h3>
|
||
<p class="text-gray-600">管理和监控您的 API 密钥</p>
|
||
</div>
|
||
<button
|
||
@click.stop="openCreateApiKeyModal"
|
||
class="btn btn-primary px-6 py-3 flex items-center gap-2"
|
||
>
|
||
<i class="fas fa-plus"></i>创建新 Key
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="apiKeysLoading" class="text-center py-12">
|
||
<div class="loading-spinner mx-auto mb-4"></div>
|
||
<p class="text-gray-500">正在加载 API Keys...</p>
|
||
</div>
|
||
|
||
<div v-else-if="apiKeys.length === 0" class="text-center py-12">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
|
||
<i class="fas fa-key text-gray-400 text-xl"></i>
|
||
</div>
|
||
<p class="text-gray-500 text-lg">暂无 API Keys</p>
|
||
<p class="text-gray-400 text-sm mt-2">点击上方按钮创建您的第一个 API Key</p>
|
||
</div>
|
||
|
||
<div v-else class="table-container">
|
||
<table class="min-w-full">
|
||
<thead class="bg-gray-50/80 backdrop-blur-sm">
|
||
<tr>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">名称</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">API Key</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">状态</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">使用统计</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">创建时间</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-200/50">
|
||
<template v-for="key in apiKeys" :key="key.id">
|
||
<!-- API Key 主行 -->
|
||
<tr class="table-row">
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="flex items-center">
|
||
<div class="w-8 h-8 bg-gradient-to-br from-blue-500 to-blue-600 rounded-lg flex items-center justify-center mr-3">
|
||
<i class="fas fa-key text-white text-xs"></i>
|
||
</div>
|
||
<div>
|
||
<div class="text-sm font-semibold text-gray-900">{{ key.name }}</div>
|
||
<div class="text-xs text-gray-500">{{ key.id }}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="text-sm font-mono text-gray-600 bg-gray-50 px-3 py-1 rounded-lg">
|
||
{{ (key.apiKey || '').substring(0, 20) }}...
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span :class="['inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold',
|
||
key.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800']">
|
||
<div :class="['w-2 h-2 rounded-full mr-2',
|
||
key.isActive ? 'bg-green-500' : 'bg-red-500']"></div>
|
||
{{ key.isActive ? '活跃' : '禁用' }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4">
|
||
<div class="space-y-1">
|
||
<!-- 请求统计 -->
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-gray-600">请求数:</span>
|
||
<span class="font-medium text-gray-900">{{ formatNumber((key.usage && key.usage.total && key.usage.total.requests) || 0) }}</span>
|
||
</div>
|
||
<!-- Token统计 -->
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-gray-600">Token:</span>
|
||
<span class="font-medium text-gray-900">{{ formatNumber((key.usage && key.usage.total && key.usage.total.tokens) || 0) }}</span>
|
||
</div>
|
||
<!-- 费用统计 -->
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-gray-600">费用:</span>
|
||
<span class="font-medium text-green-600">{{ calculateApiKeyCost(key.usage) }}</span>
|
||
</div>
|
||
<!-- 并发限制 -->
|
||
<div class="flex justify-between text-sm">
|
||
<span class="text-gray-600">并发限制:</span>
|
||
<span class="font-medium text-purple-600">{{ key.concurrencyLimit > 0 ? key.concurrencyLimit : '无限制' }}</span>
|
||
</div>
|
||
<!-- 输入/输出Token -->
|
||
<div class="flex justify-between text-xs text-gray-500">
|
||
<span>输入: {{ formatNumber((key.usage && key.usage.total && key.usage.total.inputTokens) || 0) }}</span>
|
||
<span>输出: {{ formatNumber((key.usage && key.usage.total && key.usage.total.outputTokens) || 0) }}</span>
|
||
</div>
|
||
<!-- RPM/TPM -->
|
||
<div class="flex justify-between text-xs text-blue-600">
|
||
<span>RPM: {{ (key.usage && key.usage.averages && key.usage.averages.rpm) || 0 }}</span>
|
||
<span>TPM: {{ (key.usage && key.usage.averages && key.usage.averages.tpm) || 0 }}</span>
|
||
</div>
|
||
<!-- 今日统计 -->
|
||
<div class="pt-1 border-t border-gray-100">
|
||
<div class="flex justify-between text-xs text-green-600">
|
||
<span>今日: {{ formatNumber((key.usage && key.usage.daily && key.usage.daily.requests) || 0) }}次</span>
|
||
<span>{{ formatNumber((key.usage && key.usage.daily && key.usage.daily.tokens) || 0) }}T</span>
|
||
</div>
|
||
</div>
|
||
<!-- 模型分布按钮 -->
|
||
<div class="pt-2">
|
||
<button @click="toggleApiKeyModelStats(key.id)" v-if="key && key.id" class="text-xs text-indigo-600 hover:text-indigo-800 font-medium">
|
||
<i :class="['fas', expandedApiKeys[key.id] ? 'fa-chevron-up' : 'fa-chevron-down', 'mr-1']"></i>
|
||
模型使用分布
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||
{{ new Date(key.createdAt).toLocaleDateString() }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm">
|
||
<div class="flex gap-2">
|
||
<button
|
||
@click="openEditApiKeyModal(key)"
|
||
class="text-blue-600 hover:text-blue-900 font-medium hover:bg-blue-50 px-3 py-1 rounded-lg transition-colors"
|
||
>
|
||
<i class="fas fa-edit mr-1"></i>编辑
|
||
</button>
|
||
<button
|
||
@click="deleteApiKey(key.id)"
|
||
class="text-red-600 hover:text-red-900 font-medium hover:bg-red-50 px-3 py-1 rounded-lg transition-colors"
|
||
>
|
||
<i class="fas fa-trash mr-1"></i>删除
|
||
</button>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
|
||
<!-- 模型统计展开区域 -->
|
||
<tr v-if="key && key.id && expandedApiKeys[key.id]">
|
||
<td colspan="6" class="px-6 py-4 bg-gray-50">
|
||
<div v-if="!apiKeyModelStats[key.id]" class="text-center py-4">
|
||
<div class="loading-spinner mx-auto"></div>
|
||
<p class="text-sm text-gray-500 mt-2">加载模型统计...</p>
|
||
</div>
|
||
<div class="space-y-4">
|
||
<!-- 通用的标题和时间筛选器,无论是否有数据都显示 -->
|
||
<div class="flex items-center justify-between mb-4">
|
||
<h5 class="text-sm font-semibold text-gray-700 flex items-center">
|
||
<i class="fas fa-chart-pie text-indigo-500 mr-2"></i>
|
||
模型使用分布
|
||
</h5>
|
||
<div class="flex items-center gap-2">
|
||
<span v-if="apiKeyModelStats[key.id] && apiKeyModelStats[key.id].length > 0" class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
|
||
{{ apiKeyModelStats[key.id].length }} 个模型
|
||
</span>
|
||
|
||
<!-- API Keys日期筛选器 -->
|
||
<div class="flex gap-1 items-center">
|
||
<!-- 快捷日期选择 -->
|
||
<div class="flex gap-1 bg-gray-100 rounded p-1">
|
||
<button
|
||
v-for="option in getApiKeyDateFilter(key.id).presetOptions"
|
||
:key="option.value"
|
||
@click="setApiKeyDateFilterPreset(option.value, key.id)"
|
||
:class="[
|
||
'px-2 py-1 rounded text-xs font-medium transition-colors',
|
||
getApiKeyDateFilter(key.id).preset === option.value && getApiKeyDateFilter(key.id).type === 'preset'
|
||
? 'bg-white text-blue-600 shadow-sm'
|
||
: 'text-gray-600 hover:text-gray-900'
|
||
]"
|
||
>
|
||
{{ option.label }}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Element Plus 日期范围选择器 -->
|
||
<el-date-picker
|
||
:model-value="getApiKeyDateFilter(key.id).customRange"
|
||
@update:model-value="(value) => onApiKeyCustomDateRangeChange(key.id)(value)"
|
||
type="datetimerange"
|
||
range-separator="至"
|
||
start-placeholder="开始日期"
|
||
end-placeholder="结束日期"
|
||
format="YYYY-MM-DD HH:mm:ss"
|
||
value-format="YYYY-MM-DD HH:mm:ss"
|
||
:disabled-date="disabledDate"
|
||
size="small"
|
||
style="width: 280px;"
|
||
class="api-key-date-picker"
|
||
:clearable="true"
|
||
:unlink-panels="false"
|
||
@visible-change="(visible) => !visible && $forceUpdate()"
|
||
></el-date-picker>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 数据展示区域 -->
|
||
<div v-if="apiKeyModelStats[key.id] && apiKeyModelStats[key.id].length === 0" class="text-center py-8">
|
||
<div class="flex items-center justify-center gap-2 mb-3">
|
||
<i class="fas fa-chart-line text-gray-400 text-lg"></i>
|
||
<p class="text-sm text-gray-500">暂无模型使用数据</p>
|
||
<button
|
||
@click="resetApiKeyDateFilter(key.id)"
|
||
class="text-blue-500 hover:text-blue-700 text-sm ml-2 flex items-center gap-1 transition-colors"
|
||
title="重置筛选条件并刷新"
|
||
>
|
||
<i class="fas fa-sync-alt text-xs"></i>
|
||
<span class="text-xs">刷新</span>
|
||
</button>
|
||
</div>
|
||
<p class="text-xs text-gray-400">尝试调整时间范围或点击刷新重新加载数据</p>
|
||
</div>
|
||
<div v-else-if="apiKeyModelStats[key.id] && apiKeyModelStats[key.id].length > 0" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||
<div v-for="stat in apiKeyModelStats[key.id]" :key="stat.model"
|
||
class="bg-gradient-to-br from-white to-gray-50 rounded-xl p-4 border border-gray-200 hover:border-indigo-300 hover:shadow-lg transition-all duration-200">
|
||
<div class="flex justify-between items-start mb-3">
|
||
<div class="flex-1">
|
||
<span class="text-sm font-semibold text-gray-800 block mb-1">{{ stat.model }}</span>
|
||
<span class="text-xs text-gray-500 bg-blue-50 px-2 py-1 rounded-full">{{ stat.requests }} 次请求</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-2 mb-3">
|
||
<div class="flex justify-between items-center text-sm">
|
||
<span class="text-gray-600 flex items-center">
|
||
<i class="fas fa-coins text-yellow-500 mr-1 text-xs"></i>
|
||
总Token:
|
||
</span>
|
||
<span class="font-semibold text-gray-900">{{ formatNumber(stat.allTokens) }}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center text-sm">
|
||
<span class="text-gray-600 flex items-center">
|
||
<i class="fas fa-dollar-sign text-green-500 mr-1 text-xs"></i>
|
||
费用:
|
||
</span>
|
||
<span class="font-semibold text-green-600">{{ calculateModelCost(stat) }}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center text-xs text-gray-500">
|
||
<span class="flex items-center">
|
||
<i class="fas fa-arrow-down text-green-500 mr-1"></i>
|
||
输入:
|
||
</span>
|
||
<span class="font-medium">{{ formatNumber(stat.inputTokens) }}</span>
|
||
</div>
|
||
<div class="flex justify-between items-center text-xs text-gray-500">
|
||
<span class="flex items-center">
|
||
<i class="fas fa-arrow-up text-blue-500 mr-1"></i>
|
||
输出:
|
||
</span>
|
||
<span class="font-medium">{{ formatNumber(stat.outputTokens) }}</span>
|
||
</div>
|
||
<div v-if="stat.cacheCreateTokens > 0 || stat.cacheReadTokens > 0" class="pt-1 border-t border-gray-100">
|
||
<div v-if="stat.cacheCreateTokens > 0" class="flex justify-between items-center text-xs text-purple-600">
|
||
<span class="flex items-center">
|
||
<i class="fas fa-save mr-1"></i>
|
||
缓存创建:
|
||
</span>
|
||
<span class="font-medium">{{ formatNumber(stat.cacheCreateTokens) }}</span>
|
||
</div>
|
||
<div v-if="stat.cacheReadTokens > 0" class="flex justify-between items-center text-xs text-purple-600">
|
||
<span class="flex items-center">
|
||
<i class="fas fa-download mr-1"></i>
|
||
缓存读取:
|
||
</span>
|
||
<span class="font-medium">{{ formatNumber(stat.cacheReadTokens) }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 进度条 -->
|
||
<div class="w-full bg-gray-200 rounded-full h-2 mt-3">
|
||
<div class="bg-gradient-to-r from-indigo-500 to-purple-600 h-2 rounded-full transition-all duration-500"
|
||
:style="{ width: calculateApiKeyModelPercentage(stat.allTokens, apiKeyModelStats[key.id]) + '%' }">
|
||
</div>
|
||
</div>
|
||
<div class="text-right mt-1">
|
||
<span class="text-xs font-medium text-indigo-600">
|
||
{{ calculateApiKeyModelPercentage(stat.allTokens, apiKeyModelStats[key.id]) }}%
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 总计统计,仅在有数据时显示 -->
|
||
<div v-if="apiKeyModelStats[key.id] && apiKeyModelStats[key.id].length > 0" class="mt-4 p-3 bg-gradient-to-r from-indigo-50 to-purple-50 rounded-lg border border-indigo-100">
|
||
<div class="flex items-center justify-between text-sm">
|
||
<span class="font-semibold text-gray-700 flex items-center">
|
||
<i class="fas fa-calculator text-indigo-500 mr-2"></i>
|
||
总计统计
|
||
</span>
|
||
<div class="flex gap-4 text-xs">
|
||
<span class="text-gray-600">
|
||
总请求: <span class="font-semibold text-gray-800">{{ apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.requests, 0) }}</span>
|
||
</span>
|
||
<span class="text-gray-600">
|
||
总Token: <span class="font-semibold text-gray-800">{{ formatNumber(apiKeyModelStats[key.id].reduce((sum, stat) => sum + stat.allTokens, 0)) }}</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</template>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Claude 账户管理 -->
|
||
<div v-if="activeTab === 'accounts'" class="tab-content">
|
||
<div class="card p-6">
|
||
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
|
||
<div>
|
||
<h3 class="text-xl font-bold text-gray-900 mb-2">Claude 账户管理</h3>
|
||
<p class="text-gray-600">管理您的 Claude 账户和代理配置</p>
|
||
</div>
|
||
<button
|
||
@click.stop="openCreateAccountModal"
|
||
class="btn btn-success px-6 py-3 flex items-center gap-2"
|
||
>
|
||
<i class="fas fa-plus"></i>添加账户
|
||
</button>
|
||
</div>
|
||
|
||
<div v-if="accountsLoading" class="text-center py-12">
|
||
<div class="loading-spinner mx-auto mb-4"></div>
|
||
<p class="text-gray-500">正在加载 Claude 账户...</p>
|
||
</div>
|
||
|
||
<div v-else-if="accounts.length === 0" class="text-center py-12">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
|
||
<i class="fas fa-user-circle text-gray-400 text-xl"></i>
|
||
</div>
|
||
<p class="text-gray-500 text-lg">暂无 Claude 账户</p>
|
||
<p class="text-gray-400 text-sm mt-2">点击上方按钮添加您的第一个账户</p>
|
||
</div>
|
||
|
||
<div v-else class="table-container">
|
||
<table class="min-w-full">
|
||
<thead class="bg-gray-50/80 backdrop-blur-sm">
|
||
<tr>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">名称</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">类型</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">状态</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">代理</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">最后使用</th>
|
||
<th class="px-6 py-4 text-left text-xs font-bold text-gray-700 uppercase tracking-wider">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-200/50">
|
||
<tr v-for="account in accounts" :key="account.id" class="table-row">
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="flex items-center">
|
||
<div class="w-8 h-8 bg-gradient-to-br from-green-500 to-green-600 rounded-lg flex items-center justify-center mr-3">
|
||
<i class="fas fa-user-circle text-white text-xs"></i>
|
||
</div>
|
||
<div>
|
||
<div class="text-sm font-semibold text-gray-900">{{ account.name }}</div>
|
||
<div class="text-xs text-gray-500">{{ account.id }}</div>
|
||
</div>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span v-if="account.scopes && account.scopes.length > 0"
|
||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-blue-100 text-blue-800">
|
||
<i class="fas fa-lock mr-1"></i>OAuth
|
||
</span>
|
||
<span v-else
|
||
class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold bg-orange-100 text-orange-800">
|
||
<i class="fas fa-key mr-1"></i>传统
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span :class="['inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold',
|
||
account.isActive ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800']">
|
||
<div :class="['w-2 h-2 rounded-full mr-2',
|
||
account.isActive ? 'bg-green-500' : 'bg-red-500']"></div>
|
||
{{ account.isActive ? '正常' : '异常' }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-600">
|
||
<div v-if="account.proxy" class="text-xs bg-blue-50 px-2 py-1 rounded">
|
||
{{ account.proxy.type }}://{{ account.proxy.host }}:{{ account.proxy.port }}
|
||
</div>
|
||
<div v-else class="text-gray-400">无代理</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||
{{ account.lastUsedAt ? new Date(account.lastUsedAt).toLocaleDateString() : '从未使用' }}
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-sm space-x-2">
|
||
<button
|
||
@click="openEditAccountModal(account)"
|
||
class="text-blue-600 hover:text-blue-900 font-medium hover:bg-blue-50 px-3 py-1 rounded-lg transition-colors"
|
||
>
|
||
<i class="fas fa-edit mr-1"></i>编辑
|
||
</button>
|
||
<button
|
||
@click="deleteAccount(account.id)"
|
||
class="text-red-600 hover:text-red-900 font-medium hover:bg-red-50 px-3 py-1 rounded-lg transition-colors"
|
||
>
|
||
<i class="fas fa-trash mr-1"></i>删除
|
||
</button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 模型统计 -->
|
||
<div v-if="activeTab === 'models'" class="tab-content">
|
||
<div class="card p-6">
|
||
<div class="flex flex-col md:flex-row justify-between items-center gap-4 mb-6">
|
||
<div>
|
||
<h3 class="text-xl font-bold text-gray-900 mb-2">模型使用统计</h3>
|
||
<p class="text-gray-600">查看不同模型的使用量和费用统计</p>
|
||
</div>
|
||
<div class="flex gap-2">
|
||
<select v-model="modelStatsPeriod" @change="loadModelStats()" class="form-input px-4 py-2">
|
||
<option value="daily">今日</option>
|
||
<option value="monthly">本月</option>
|
||
</select>
|
||
<button @click="loadModelStats()" class="btn btn-primary px-4 py-2 flex items-center gap-2">
|
||
<i class="fas fa-sync-alt"></i>刷新
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="modelStatsLoading" class="text-center py-12">
|
||
<div class="loading-spinner mx-auto mb-4"></div>
|
||
<p class="text-gray-500">正在加载模型统计...</p>
|
||
</div>
|
||
|
||
<div v-else-if="modelStats.length === 0" class="text-center py-12">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-gray-100 rounded-full flex items-center justify-center">
|
||
<i class="fas fa-chart-pie text-gray-400 text-xl"></i>
|
||
</div>
|
||
<p class="text-gray-500 text-lg">暂无模型使用数据</p>
|
||
</div>
|
||
|
||
<div v-else class="space-y-4">
|
||
<div v-for="stat in modelStats" :key="stat.model" class="card p-6 bg-gradient-to-r from-blue-50 to-purple-50">
|
||
<div class="flex justify-between items-start mb-4">
|
||
<div>
|
||
<h4 class="text-lg font-bold text-gray-900 mb-1">{{ stat.model }}</h4>
|
||
<p class="text-sm text-gray-600">{{ stat.period === 'daily' ? '今日使用' : '本月使用' }}</p>
|
||
</div>
|
||
<div class="text-right">
|
||
<div class="text-2xl font-bold text-green-600">{{ (stat.formatted && stat.formatted.total) || '$0.000000' }}</div>
|
||
<div class="text-sm text-gray-500">总费用</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-4">
|
||
<div class="text-center p-3 bg-white rounded-lg">
|
||
<div class="text-lg font-semibold text-blue-600">{{ formatNumber((stat.usage && stat.usage.requests) || 0) }}</div>
|
||
<div class="text-xs text-gray-500">请求数</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-white rounded-lg">
|
||
<div class="text-lg font-semibold text-green-600">{{ formatNumber((stat.usage && stat.usage.inputTokens) || 0) }}</div>
|
||
<div class="text-xs text-gray-500">输入Token</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-white rounded-lg">
|
||
<div class="text-lg font-semibold text-orange-600">{{ formatNumber((stat.usage && stat.usage.outputTokens) || 0) }}</div>
|
||
<div class="text-xs text-gray-500">输出Token</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-white rounded-lg">
|
||
<div class="text-lg font-semibold text-purple-600">{{ formatNumber((stat.usage && stat.usage.totalTokens) || 0) }}</div>
|
||
<div class="text-xs text-gray-500">总Token</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Cache Token统计 -->
|
||
<div v-if="((stat.usage && stat.usage.cacheCreateTokens) || 0) > 0 || ((stat.usage && stat.usage.cacheReadTokens) || 0) > 0" class="grid grid-cols-2 gap-4 mb-4">
|
||
<div class="text-center p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||
<div class="text-lg font-semibold text-blue-600">{{ formatNumber((stat.usage && stat.usage.cacheCreateTokens) || 0) }}</div>
|
||
<div class="text-xs text-blue-500">缓存创建</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||
<div class="text-lg font-semibold text-blue-600">{{ formatNumber((stat.usage && stat.usage.cacheReadTokens) || 0) }}</div>
|
||
<div class="text-xs text-blue-500">缓存读取</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 费用明细 -->
|
||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div class="text-center p-3 bg-green-50 rounded-lg border border-green-200">
|
||
<div class="text-sm font-semibold text-green-600">{{ (stat.formatted && stat.formatted.input) || '$0.000000' }}</div>
|
||
<div class="text-xs text-green-500">输入费用</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-orange-50 rounded-lg border border-orange-200">
|
||
<div class="text-sm font-semibold text-orange-600">{{ (stat.formatted && stat.formatted.output) || '$0.000000' }}</div>
|
||
<div class="text-xs text-orange-500">输出费用</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-blue-50 rounded-lg border border-blue-200">
|
||
<div class="text-sm font-semibold text-blue-600">{{ (stat.formatted && stat.formatted.cacheWrite) || '$0.000000' }}</div>
|
||
<div class="text-xs text-blue-500">缓存写入</div>
|
||
</div>
|
||
<div class="text-center p-3 bg-purple-50 rounded-lg border border-purple-200">
|
||
<div class="text-sm font-semibold text-purple-600">{{ (stat.formatted && stat.formatted.cacheRead) || '$0.000000' }}</div>
|
||
<div class="text-xs text-purple-500">缓存读取</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 定价信息 -->
|
||
<div class="mt-4 p-3 bg-gray-50 rounded-lg">
|
||
<div class="text-xs text-gray-600 mb-2">定价信息 (USD per 1M tokens):</div>
|
||
<div class="grid grid-cols-4 gap-2 text-xs">
|
||
<div>输入: ${{ (stat.pricing && stat.pricing.input) || 0 }}</div>
|
||
<div>输出: ${{ (stat.pricing && stat.pricing.output) || 0 }}</div>
|
||
<div>缓存写: ${{ (stat.pricing && stat.pricing.cacheWrite) || 0 }}</div>
|
||
<div>缓存读: ${{ (stat.pricing && stat.pricing.cacheRead) || 0 }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div v-if="activeTab === 'tutorial'" class="tab-content">
|
||
<div class="card p-6">
|
||
<div class="mb-8">
|
||
<h3 class="text-2xl font-bold text-gray-900 mb-4 flex items-center">
|
||
<i class="fas fa-graduation-cap text-blue-600 mr-3"></i>
|
||
Claude Code 使用教程
|
||
</h3>
|
||
<p class="text-gray-600 text-lg">跟着这个教程,你可以轻松在自己的电脑上安装并使用 Claude Code。</p>
|
||
</div>
|
||
|
||
<!-- 系统选择标签 -->
|
||
<div class="mb-8">
|
||
<div class="flex flex-wrap gap-2 p-2 bg-gray-100 rounded-xl">
|
||
<button
|
||
v-for="system in tutorialSystems"
|
||
:key="system.key"
|
||
@click="activeTutorialSystem = system.key"
|
||
:class="['flex-1 py-3 px-6 text-sm font-semibold rounded-lg transition-all duration-300 flex items-center justify-center gap-2',
|
||
activeTutorialSystem === system.key
|
||
? 'bg-white text-blue-600 shadow-sm'
|
||
: 'text-gray-600 hover:bg-white/50 hover:text-gray-900']"
|
||
>
|
||
<i :class="system.icon"></i>
|
||
{{ system.name }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Windows 教程 -->
|
||
<div v-if="activeTutorialSystem === 'windows'" class="tutorial-content">
|
||
<!-- 第一步:安装 Node.js -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">1</span>
|
||
安装 Node.js 环境
|
||
</h4>
|
||
<p class="text-gray-600 mb-6">Claude Code 需要 Node.js 环境才能运行。</p>
|
||
|
||
<div class="bg-gradient-to-r from-blue-50 to-indigo-50 rounded-xl p-6 border border-blue-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fab fa-windows text-blue-600 mr-2"></i>
|
||
Windows 安装方法
|
||
</h5>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法一:官网下载(推荐)</p>
|
||
<ol class="list-decimal list-inside text-gray-600 space-y-2 ml-4">
|
||
<li>打开浏览器访问 <code class="bg-gray-100 px-2 py-1 rounded text-sm">https://nodejs.org/</code></li>
|
||
<li>点击 "LTS" 版本进行下载(推荐长期支持版本)</li>
|
||
<li>下载完成后双击 <code class="bg-gray-100 px-2 py-1 rounded text-sm">.msi</code> 文件</li>
|
||
<li>按照安装向导完成安装,保持默认设置即可</li>
|
||
</ol>
|
||
</div>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法二:使用包管理器</p>
|
||
<p class="text-gray-600 mb-2">如果你安装了 Chocolatey 或 Scoop,可以使用命令行安装:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="mb-2"># 使用 Chocolatey</div>
|
||
<div class="text-gray-300">choco install nodejs</div>
|
||
<div class="mt-3 mb-2"># 或使用 Scoop</div>
|
||
<div class="text-gray-300">scoop install nodejs</div>
|
||
</div>
|
||
</div>
|
||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-blue-800 mb-2">Windows 注意事项</h6>
|
||
<ul class="text-blue-700 text-sm space-y-1">
|
||
<li>• 建议使用 PowerShell 而不是 CMD</li>
|
||
<li>• 如果遇到权限问题,尝试以管理员身份运行</li>
|
||
<li>• 某些杀毒软件可能会误报,需要添加白名单</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证安装是否成功</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,打开 PowerShell 或 CMD,输入以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">node --version</div>
|
||
<div class="text-gray-300">npm --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,说明安装成功了!</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二步:安装 Git Bash -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">2</span>
|
||
安装 Git Bash
|
||
</h4>
|
||
<p class="text-gray-600 mb-6">Windows 环境下需要使用 Git Bash 安装Claude code。安装完成后,环境变量设置和使用 Claude Code 仍然在普通的 PowerShell 或 CMD 中进行。</p>
|
||
|
||
<div class="bg-gradient-to-r from-green-50 to-emerald-50 rounded-xl p-6 border border-green-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fab fa-git-alt text-green-600 mr-2"></i>
|
||
下载并安装 Git for Windows
|
||
</h5>
|
||
<ol class="list-decimal list-inside text-gray-600 space-y-2 ml-4 mb-4">
|
||
<li>访问 <code class="bg-gray-100 px-2 py-1 rounded text-sm">https://git-scm.com/downloads/win</code></li>
|
||
<li>点击 "Download for Windows" 下载安装包</li>
|
||
<li>运行下载的 <code class="bg-gray-100 px-2 py-1 rounded text-sm">.exe</code> 安装文件</li>
|
||
<li>在安装过程中保持默认设置,直接点击 "Next" 完成安装</li>
|
||
</ol>
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">安装完成后</h6>
|
||
<ul class="text-green-700 text-sm space-y-1">
|
||
<li>• 在任意文件夹右键可以看到 "Git Bash Here" 选项</li>
|
||
<li>• 也可以从开始菜单启动 "Git Bash"</li>
|
||
<li>• 只需要在 Git Bash 中运行 npm install 命令</li>
|
||
<li>• 后续的环境变量设置和使用都在 PowerShell/CMD 中</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证 Git Bash 安装</h6>
|
||
<p class="text-green-700 text-sm mb-3">打开 Git Bash,输入以下命令验证:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">git --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示 Git 版本号,说明安装成功!</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第三步:安装 Claude Code -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-purple-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">3</span>
|
||
安装 Claude Code
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl p-6 border border-purple-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-download text-purple-600 mr-2"></i>
|
||
安装 Claude Code
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">打开 Git Bash(重要:不要使用 PowerShell),运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm mb-4">
|
||
<div class="mb-2"># 在 Git Bash 中全局安装 Claude Code</div>
|
||
<div class="text-gray-300">npm install -g @anthropic-ai/claude-code</div>
|
||
</div>
|
||
<p class="text-gray-600 text-sm">这个命令会从 npm 官方仓库下载并安装最新版本的 Claude Code。</p>
|
||
|
||
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mt-4">
|
||
<h6 class="font-medium text-yellow-800 mb-2">重要提醒</h6>
|
||
<ul class="text-yellow-700 text-sm space-y-1">
|
||
<li>• 必须在 Git Bash 中运行,不要在 PowerShell 中运行</li>
|
||
<li>• 如果遇到权限问题,可以尝试在 Git Bash 中使用 sudo 命令</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证 Claude Code 安装</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,输入以下命令检查是否安装成功:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,恭喜你!Claude Code 已经成功安装了。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第四步:设置环境变量 -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">4</span>
|
||
设置环境变量
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-orange-50 to-yellow-50 rounded-xl p-6 border border-orange-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-cog text-orange-600 mr-2"></i>
|
||
配置 Claude Code 环境变量
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:</p>
|
||
|
||
<div class="space-y-4">
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法一:PowerShell 临时设置(推荐)</h6>
|
||
<p class="text-gray-600 text-sm mb-3">在 PowerShell 中运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">$env:ANTHROPIC_BASE_URL = "{{ currentBaseUrl }}"</div>
|
||
<div class="text-gray-300">$env:ANTHROPIC_AUTH_TOKEN = "你的API密钥"</div>
|
||
</div>
|
||
<p class="text-yellow-700 text-xs mt-2">💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法二:系统环境变量(永久设置)</h6>
|
||
<ol class="text-gray-600 text-sm space-y-1 list-decimal list-inside">
|
||
<li>右键"此电脑" → "属性" → "高级系统设置"</li>
|
||
<li>点击"环境变量"按钮</li>
|
||
<li>在"用户变量"或"系统变量"中点击"新建"</li>
|
||
<li>添加以下两个变量:</li>
|
||
</ol>
|
||
<div class="mt-3 space-y-2">
|
||
<div class="bg-gray-100 p-2 rounded text-sm">
|
||
<strong>变量名:</strong> ANTHROPIC_BASE_URL<br>
|
||
<strong>变量值:</strong> <span class="font-mono">{{ currentBaseUrl }}</span>
|
||
</div>
|
||
<div class="bg-gray-100 p-2 rounded text-sm">
|
||
<strong>变量名:</strong> ANTHROPIC_AUTH_TOKEN<br>
|
||
<strong>变量值:</strong> <span class="font-mono">你的API密钥</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证环境变量设置 -->
|
||
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mt-6">
|
||
<h6 class="font-medium text-blue-800 mb-2">验证环境变量设置</h6>
|
||
<p class="text-blue-700 text-sm mb-3">设置完环境变量后,可以通过以下命令验证是否设置成功:</p>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">在 PowerShell 中验证:</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm space-y-1">
|
||
<div class="text-gray-300">echo $env:ANTHROPIC_BASE_URL</div>
|
||
<div class="text-gray-300">echo $env:ANTHROPIC_AUTH_TOKEN</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">在 CMD 中验证:</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm space-y-1">
|
||
<div class="text-gray-300">echo %ANTHROPIC_BASE_URL%</div>
|
||
<div class="text-gray-300">echo %ANTHROPIC_AUTH_TOKEN%</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mt-3 space-y-2">
|
||
<p class="text-blue-700 text-sm">
|
||
<strong>预期输出示例:</strong>
|
||
</p>
|
||
<div class="bg-gray-100 p-2 rounded text-sm font-mono">
|
||
<div>{{ currentBaseUrl }}</div>
|
||
<div>cr_xxxxxxxxxxxxxxxxxx</div>
|
||
</div>
|
||
<p class="text-blue-700 text-xs">
|
||
💡 如果输出为空或显示变量名本身,说明环境变量设置失败,请重新设置。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第五步:开始使用 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-yellow-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">5</span>
|
||
开始使用 Claude Code
|
||
</h4>
|
||
<div class="bg-gradient-to-r from-yellow-50 to-amber-50 rounded-xl p-6 border border-yellow-100">
|
||
<p class="text-gray-700 mb-4">现在你可以开始使用 Claude Code 了!</p>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">启动 Claude Code</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">在特定项目中使用</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># 进入你的项目目录</div>
|
||
<div class="text-gray-300">cd C:\path\to\your\project</div>
|
||
<div class="mt-2 mb-2"># 启动 Claude Code</div>
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Windows 故障排除 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<i class="fas fa-wrench text-red-600 mr-3"></i>
|
||
Windows 常见问题解决
|
||
</h4>
|
||
<div class="space-y-4">
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
安装时提示 "permission denied" 错误
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">这通常是权限问题,尝试以下解决方法:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>以管理员身份运行 PowerShell</li>
|
||
<li>或者配置 npm 使用用户目录:<code class="bg-gray-200 px-1 rounded">npm config set prefix %APPDATA%\npm</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
PowerShell 执行策略错误
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">如果遇到执行策略限制,运行:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
环境变量设置后不生效
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">设置永久环境变量后需要:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>重新启动 PowerShell 或 CMD</li>
|
||
<li>或者注销并重新登录 Windows</li>
|
||
<li>验证设置:<code class="bg-gray-200 px-1 rounded">echo $env:ANTHROPIC_BASE_URL</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- macOS 教程 -->
|
||
<div v-if="activeTutorialSystem === 'macos'" class="tutorial-content">
|
||
<!-- 第一步:安装 Node.js -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">1</span>
|
||
安装 Node.js 环境
|
||
</h4>
|
||
<p class="text-gray-600 mb-6">Claude Code 需要 Node.js 环境才能运行。</p>
|
||
|
||
<div class="bg-gradient-to-r from-gray-50 to-slate-50 rounded-xl p-6 border border-gray-200 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fab fa-apple text-gray-700 mr-2"></i>
|
||
macOS 安装方法
|
||
</h5>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法一:使用 Homebrew(推荐)</p>
|
||
<p class="text-gray-600 mb-2">如果你已经安装了 Homebrew,使用它安装 Node.js 会更方便:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="mb-2"># 更新 Homebrew</div>
|
||
<div class="text-gray-300">brew update</div>
|
||
<div class="mt-3 mb-2"># 安装 Node.js</div>
|
||
<div class="text-gray-300">brew install node</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法二:官网下载</p>
|
||
<ol class="list-decimal list-inside text-gray-600 space-y-2 ml-4">
|
||
<li>访问 <code class="bg-gray-100 px-2 py-1 rounded text-sm">https://nodejs.org/</code></li>
|
||
<li>下载适合 macOS 的 LTS 版本</li>
|
||
<li>打开下载的 <code class="bg-gray-100 px-2 py-1 rounded text-sm">.pkg</code> 文件</li>
|
||
<li>按照安装程序指引完成安装</li>
|
||
</ol>
|
||
</div>
|
||
<div class="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-gray-800 mb-2">macOS 注意事项</h6>
|
||
<ul class="text-gray-700 text-sm space-y-1">
|
||
<li>• 如果遇到权限问题,可能需要使用 <code class="bg-gray-200 px-1 rounded">sudo</code></li>
|
||
<li>• 首次运行可能需要在系统偏好设置中允许</li>
|
||
<li>• 建议使用 Terminal 或 iTerm2</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证安装是否成功</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,打开 Terminal,输入以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">node --version</div>
|
||
<div class="text-gray-300">npm --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,说明安装成功了!</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二步:安装 Claude Code -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">2</span>
|
||
安装 Claude Code
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl p-6 border border-purple-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-download text-purple-600 mr-2"></i>
|
||
安装 Claude Code
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">打开 Terminal,运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm mb-4">
|
||
<div class="mb-2"># 全局安装 Claude Code</div>
|
||
<div class="text-gray-300">npm install -g @anthropic-ai/claude-code</div>
|
||
</div>
|
||
<p class="text-gray-600 text-sm mb-2">如果遇到权限问题,可以使用 sudo:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="text-gray-300">sudo npm install -g @anthropic-ai/claude-code</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证 Claude Code 安装</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,输入以下命令检查是否安装成功:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,恭喜你!Claude Code 已经成功安装了。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第三步:设置环境变量 -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">3</span>
|
||
设置环境变量
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-orange-50 to-yellow-50 rounded-xl p-6 border border-orange-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-cog text-orange-600 mr-2"></i>
|
||
配置 Claude Code 环境变量
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:</p>
|
||
|
||
<div class="space-y-4">
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法一:临时设置(当前会话)</h6>
|
||
<p class="text-gray-600 text-sm mb-3">在 Terminal 中运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"</div>
|
||
<div class="text-gray-300">export ANTHROPIC_AUTH_TOKEN="你的API密钥"</div>
|
||
</div>
|
||
<p class="text-yellow-700 text-xs mt-2">💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法二:永久设置</h6>
|
||
<p class="text-gray-600 text-sm mb-3">编辑你的 shell 配置文件(根据你使用的 shell):</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm mb-3">
|
||
<div class="mb-2"># 对于 zsh (默认)</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc</div>
|
||
<div class="text-gray-300">source ~/.zshrc</div>
|
||
</div>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># 对于 bash</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bash_profile</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bash_profile</div>
|
||
<div class="text-gray-300">source ~/.bash_profile</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第四步:开始使用 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-yellow-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">4</span>
|
||
开始使用 Claude Code
|
||
</h4>
|
||
<div class="bg-gradient-to-r from-yellow-50 to-amber-50 rounded-xl p-6 border border-yellow-100">
|
||
<p class="text-gray-700 mb-4">现在你可以开始使用 Claude Code 了!</p>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">启动 Claude Code</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">在特定项目中使用</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># 进入你的项目目录</div>
|
||
<div class="text-gray-300">cd /path/to/your/project</div>
|
||
<div class="mt-2 mb-2"># 启动 Claude Code</div>
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- macOS 故障排除 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<i class="fas fa-wrench text-red-600 mr-3"></i>
|
||
macOS 常见问题解决
|
||
</h4>
|
||
<div class="space-y-4">
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
安装时提示权限错误
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">尝试以下解决方法:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>使用 sudo 安装:<code class="bg-gray-200 px-1 rounded">sudo npm install -g @anthropic-ai/claude-code</code></li>
|
||
<li>或者配置 npm 使用用户目录:<code class="bg-gray-200 px-1 rounded">npm config set prefix ~/.npm-global</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
macOS 安全设置阻止运行
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">如果系统阻止运行 Claude Code:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>打开"系统偏好设置" → "安全性与隐私"</li>
|
||
<li>点击"仍要打开"或"允许"</li>
|
||
<li>或者在 Terminal 中运行:<code class="bg-gray-200 px-1 rounded">sudo spctl --master-disable</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
环境变量不生效
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">检查以下几点:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>确认修改了正确的配置文件(.zshrc 或 .bash_profile)</li>
|
||
<li>重新启动 Terminal</li>
|
||
<li>验证设置:<code class="bg-gray-200 px-1 rounded">echo $ANTHROPIC_BASE_URL</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Linux 教程 -->
|
||
<div v-if="activeTutorialSystem === 'linux'" class="tutorial-content">
|
||
<!-- 第一步:安装 Node.js -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-blue-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">1</span>
|
||
安装 Node.js 环境
|
||
</h4>
|
||
<p class="text-gray-600 mb-6">Claude Code 需要 Node.js 环境才能运行。</p>
|
||
|
||
<div class="bg-gradient-to-r from-orange-50 to-red-50 rounded-xl p-6 border border-orange-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fab fa-ubuntu text-orange-600 mr-2"></i>
|
||
Linux 安装方法
|
||
</h5>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法一:使用官方仓库(推荐)</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="mb-2"># 添加 NodeSource 仓库</div>
|
||
<div class="text-gray-300">curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -</div>
|
||
<div class="mt-3 mb-2"># 安装 Node.js</div>
|
||
<div class="text-gray-300">sudo apt-get install -y nodejs</div>
|
||
</div>
|
||
</div>
|
||
<div class="mb-4">
|
||
<p class="text-gray-700 mb-3">方法二:使用系统包管理器</p>
|
||
<p class="text-gray-600 mb-2">虽然版本可能不是最新的,但对于基本使用已经足够:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="mb-2"># Ubuntu/Debian</div>
|
||
<div class="text-gray-300">sudo apt update</div>
|
||
<div class="text-gray-300">sudo apt install nodejs npm</div>
|
||
<div class="mt-3 mb-2"># CentOS/RHEL/Fedora</div>
|
||
<div class="text-gray-300">sudo dnf install nodejs npm</div>
|
||
</div>
|
||
</div>
|
||
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-orange-800 mb-2">Linux 注意事项</h6>
|
||
<ul class="text-orange-700 text-sm space-y-1">
|
||
<li>• 某些发行版可能需要安装额外的依赖</li>
|
||
<li>• 如果遇到权限问题,使用 <code class="bg-orange-200 px-1 rounded">sudo</code></li>
|
||
<li>• 确保你的用户在 npm 的全局目录有写权限</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证安装是否成功</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,打开终端,输入以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">node --version</div>
|
||
<div class="text-gray-300">npm --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,说明安装成功了!</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第二步:安装 Claude Code -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-green-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">2</span>
|
||
安装 Claude Code
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl p-6 border border-purple-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-download text-purple-600 mr-2"></i>
|
||
安装 Claude Code
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">打开终端,运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm mb-4">
|
||
<div class="mb-2"># 全局安装 Claude Code</div>
|
||
<div class="text-gray-300">npm install -g @anthropic-ai/claude-code</div>
|
||
</div>
|
||
<p class="text-gray-600 text-sm mb-2">如果遇到权限问题,可以使用 sudo:</p>
|
||
<div class="bg-gray-900 text-green-400 p-4 rounded-lg font-mono text-sm">
|
||
<div class="text-gray-300">sudo npm install -g @anthropic-ai/claude-code</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 验证安装 -->
|
||
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
|
||
<h6 class="font-medium text-green-800 mb-2">验证 Claude Code 安装</h6>
|
||
<p class="text-green-700 text-sm mb-3">安装完成后,输入以下命令检查是否安装成功:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude --version</div>
|
||
</div>
|
||
<p class="text-green-700 text-sm mt-2">如果显示版本号,恭喜你!Claude Code 已经成功安装了。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第三步:设置环境变量 -->
|
||
<div class="mb-10">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">3</span>
|
||
设置环境变量
|
||
</h4>
|
||
|
||
<div class="bg-gradient-to-r from-orange-50 to-yellow-50 rounded-xl p-6 border border-orange-100 mb-6">
|
||
<h5 class="text-lg font-semibold text-gray-800 mb-3 flex items-center">
|
||
<i class="fas fa-cog text-orange-600 mr-2"></i>
|
||
配置 Claude Code 环境变量
|
||
</h5>
|
||
<p class="text-gray-700 mb-4">为了让 Claude Code 连接到你的中转服务,需要设置两个环境变量:</p>
|
||
|
||
<div class="space-y-4">
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法一:临时设置(当前会话)</h6>
|
||
<p class="text-gray-600 text-sm mb-3">在终端中运行以下命令:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"</div>
|
||
<div class="text-gray-300">export ANTHROPIC_AUTH_TOKEN="你的API密钥"</div>
|
||
</div>
|
||
<p class="text-yellow-700 text-xs mt-2">💡 记得将 "你的API密钥" 替换为在上方 "API Keys" 标签页中创建的实际密钥。</p>
|
||
</div>
|
||
|
||
<div class="bg-white rounded-lg p-4 border border-orange-200">
|
||
<h6 class="font-medium text-gray-800 mb-2">方法二:永久设置</h6>
|
||
<p class="text-gray-600 text-sm mb-3">编辑你的 shell 配置文件:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm mb-3">
|
||
<div class="mb-2"># 对于 bash (默认)</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.bashrc</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.bashrc</div>
|
||
<div class="text-gray-300">source ~/.bashrc</div>
|
||
</div>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># 对于 zsh</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_BASE_URL="{{ currentBaseUrl }}"' >> ~/.zshrc</div>
|
||
<div class="text-gray-300">echo 'export ANTHROPIC_AUTH_TOKEN="你的API密钥"' >> ~/.zshrc</div>
|
||
<div class="text-gray-300">source ~/.zshrc</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 第四步:开始使用 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<span class="w-8 h-8 bg-yellow-500 text-white rounded-full flex items-center justify-center text-sm font-bold mr-3">4</span>
|
||
开始使用 Claude Code
|
||
</h4>
|
||
<div class="bg-gradient-to-r from-yellow-50 to-amber-50 rounded-xl p-6 border border-yellow-100">
|
||
<p class="text-gray-700 mb-4">现在你可以开始使用 Claude Code 了!</p>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">启动 Claude Code</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h6 class="font-medium text-gray-800 mb-2">在特定项目中使用</h6>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># 进入你的项目目录</div>
|
||
<div class="text-gray-300">cd /path/to/your/project</div>
|
||
<div class="mt-2 mb-2"># 启动 Claude Code</div>
|
||
<div class="text-gray-300">claude</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Linux 故障排除 -->
|
||
<div class="mb-8">
|
||
<h4 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
|
||
<i class="fas fa-wrench text-red-600 mr-3"></i>
|
||
Linux 常见问题解决
|
||
</h4>
|
||
<div class="space-y-4">
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
安装时提示权限错误
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">尝试以下解决方法:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>使用 sudo 安装:<code class="bg-gray-200 px-1 rounded">sudo npm install -g @anthropic-ai/claude-code</code></li>
|
||
<li>或者配置 npm 使用用户目录:<code class="bg-gray-200 px-1 rounded">npm config set prefix ~/.npm-global</code></li>
|
||
<li>然后添加到 PATH:<code class="bg-gray-200 px-1 rounded">export PATH=~/.npm-global/bin:$PATH</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
缺少依赖库
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">某些 Linux 发行版需要安装额外依赖:</p>
|
||
<div class="bg-gray-900 text-green-400 p-3 rounded font-mono text-sm">
|
||
<div class="mb-2"># Ubuntu/Debian</div>
|
||
<div class="text-gray-300">sudo apt install build-essential</div>
|
||
<div class="mt-2 mb-2"># CentOS/RHEL</div>
|
||
<div class="text-gray-300">sudo dnf groupinstall "Development Tools"</div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
|
||
<details class="bg-gray-50 rounded-lg border border-gray-200">
|
||
<summary class="p-4 cursor-pointer font-medium text-gray-800 hover:bg-gray-100">
|
||
环境变量不生效
|
||
</summary>
|
||
<div class="px-4 pb-4 text-gray-600">
|
||
<p class="mb-2">检查以下几点:</p>
|
||
<ul class="list-disc list-inside space-y-1 text-sm">
|
||
<li>确认修改了正确的配置文件(.bashrc 或 .zshrc)</li>
|
||
<li>重新启动终端或运行 <code class="bg-gray-200 px-1 rounded">source ~/.bashrc</code></li>
|
||
<li>验证设置:<code class="bg-gray-200 px-1 rounded">echo $ANTHROPIC_BASE_URL</code></li>
|
||
</ul>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 结尾 -->
|
||
<div class="bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-xl p-6 text-center">
|
||
<h5 class="text-xl font-semibold mb-2">🎉 恭喜你!</h5>
|
||
<p class="text-blue-100 mb-4">你已经成功安装并配置了 Claude Code,现在可以开始享受 AI 编程助手带来的便利了。</p>
|
||
<p class="text-sm text-blue-200">如果在使用过程中遇到任何问题,可以查看官方文档或社区讨论获取帮助。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 创建 API Key 模态框 -->
|
||
<div v-if="showCreateApiKeyModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||
<div class="modal-content w-full max-w-md p-8 mx-auto">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center">
|
||
<i class="fas fa-key text-white"></i>
|
||
</div>
|
||
<h3 class="text-xl font-bold text-gray-900">创建新的 API Key</h3>
|
||
</div>
|
||
<button
|
||
@click="showCreateApiKeyModal = false"
|
||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<i class="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<form @submit.prevent="createApiKey" class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">名称</label>
|
||
<input
|
||
v-model="apiKeyForm.name"
|
||
type="text"
|
||
required
|
||
class="form-input w-full"
|
||
placeholder="为您的 API Key 取一个名称"
|
||
>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Token 限制 (可选)</label>
|
||
<input
|
||
v-model="apiKeyForm.tokenLimit"
|
||
type="number"
|
||
placeholder="留空表示无限制"
|
||
class="form-input w-full"
|
||
>
|
||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 的最大 token 使用量</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">并发限制 (可选)</label>
|
||
<input
|
||
v-model="apiKeyForm.concurrencyLimit"
|
||
type="number"
|
||
min="0"
|
||
placeholder="0 表示无限制"
|
||
class="form-input w-full"
|
||
>
|
||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 可同时处理的最大请求数,0 或留空表示无限制</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">备注 (可选)</label>
|
||
<textarea
|
||
v-model="apiKeyForm.description"
|
||
rows="3"
|
||
class="form-input w-full resize-none"
|
||
placeholder="描述此 API Key 的用途..."
|
||
></textarea>
|
||
</div>
|
||
|
||
<div class="flex gap-3 pt-4">
|
||
<button
|
||
type="button"
|
||
@click="showCreateApiKeyModal = false"
|
||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
:disabled="createApiKeyLoading"
|
||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
<div v-if="createApiKeyLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-plus mr-2"></i>
|
||
{{ createApiKeyLoading ? '创建中...' : '创建' }}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑 API Key 模态框 -->
|
||
<div v-if="showEditApiKeyModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||
<div class="modal-content w-full max-w-md p-8 mx-auto">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center">
|
||
<i class="fas fa-edit text-white"></i>
|
||
</div>
|
||
<h3 class="text-xl font-bold text-gray-900">编辑 API Key</h3>
|
||
</div>
|
||
<button
|
||
@click="closeEditApiKeyModal"
|
||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<i class="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<form @submit.prevent="updateApiKey" class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">名称</label>
|
||
<input
|
||
:value="editApiKeyForm.name"
|
||
type="text"
|
||
disabled
|
||
class="form-input w-full bg-gray-100 cursor-not-allowed"
|
||
>
|
||
<p class="text-xs text-gray-500 mt-2">名称不可修改</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Token 限制</label>
|
||
<input
|
||
v-model="editApiKeyForm.tokenLimit"
|
||
type="number"
|
||
min="0"
|
||
placeholder="0 表示无限制"
|
||
class="form-input w-full"
|
||
>
|
||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 的最大 token 使用量,0 或留空表示无限制</p>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">并发限制</label>
|
||
<input
|
||
v-model="editApiKeyForm.concurrencyLimit"
|
||
type="number"
|
||
min="0"
|
||
placeholder="0 表示无限制"
|
||
class="form-input w-full"
|
||
>
|
||
<p class="text-xs text-gray-500 mt-2">设置此 API Key 可同时处理的最大请求数,0 或留空表示无限制</p>
|
||
</div>
|
||
|
||
<div class="flex gap-3 pt-4">
|
||
<button
|
||
type="button"
|
||
@click="closeEditApiKeyModal"
|
||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="submit"
|
||
:disabled="editApiKeyLoading"
|
||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
<div v-if="editApiKeyLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-save mr-2"></i>
|
||
{{ editApiKeyLoading ? '保存中...' : '保存修改' }}
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 新创建的 API Key 展示弹窗 -->
|
||
<div v-if="showNewApiKeyModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||
<div class="modal-content w-full max-w-lg p-8 mx-auto">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-12 h-12 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center">
|
||
<i class="fas fa-check text-white text-lg"></i>
|
||
</div>
|
||
<div>
|
||
<h3 class="text-xl font-bold text-gray-900">API Key 创建成功</h3>
|
||
<p class="text-sm text-gray-600">请妥善保存您的 API Key</p>
|
||
</div>
|
||
</div>
|
||
<button
|
||
@click="closeNewApiKeyModal"
|
||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<i class="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 警告提示 -->
|
||
<div class="bg-amber-50 border-l-4 border-amber-400 p-4 mb-6">
|
||
<div class="flex items-start">
|
||
<div class="w-6 h-6 bg-amber-400 rounded-lg flex items-center justify-center flex-shrink-0 mt-0.5">
|
||
<i class="fas fa-exclamation-triangle text-white text-sm"></i>
|
||
</div>
|
||
<div class="ml-3">
|
||
<h5 class="font-semibold text-amber-900 mb-1">重要提醒</h5>
|
||
<p class="text-sm text-amber-800">
|
||
这是您唯一能看到完整 API Key 的机会。关闭此窗口后,系统将不再显示完整的 API Key。请立即复制并妥善保存。
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API Key 信息 -->
|
||
<div class="space-y-4 mb-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-2">API Key 名称</label>
|
||
<div class="p-3 bg-gray-50 rounded-lg border">
|
||
<span class="text-gray-900 font-medium">{{ newApiKey.name }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-2">备注</label>
|
||
<div class="p-3 bg-gray-50 rounded-lg border">
|
||
<span class="text-gray-700">{{ newApiKey.description }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-2">API Key</label>
|
||
<div class="relative">
|
||
<div class="p-4 pr-14 bg-gray-900 rounded-lg border font-mono text-sm text-white break-all min-h-[60px] flex items-center">
|
||
{{ getDisplayedApiKey() }}
|
||
</div>
|
||
<div class="absolute top-3 right-3">
|
||
<button
|
||
@click="toggleApiKeyVisibility"
|
||
class="p-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition-colors shadow-lg"
|
||
:title="newApiKey.showFullKey ? '隐藏完整Key' : '显示完整Key'"
|
||
>
|
||
<i :class="newApiKey.showFullKey ? 'fas fa-eye-slash' : 'fas fa-eye'" class="text-white text-sm"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<p class="text-xs text-gray-500 mt-2">
|
||
<i class="fas fa-info-circle mr-1"></i>
|
||
点击眼睛图标切换显示模式,使用下方按钮复制完整 API Key
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 操作按钮 -->
|
||
<div class="flex gap-3">
|
||
<button
|
||
@click="copyApiKeyToClipboard"
|
||
class="flex-1 btn btn-primary py-3 px-6 font-semibold flex items-center justify-center gap-2"
|
||
>
|
||
<i class="fas fa-copy"></i>
|
||
复制 API Key
|
||
</button>
|
||
<button
|
||
@click="closeNewApiKeyModal"
|
||
class="px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
我已保存
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 创建 Claude 账户模态框 -->
|
||
<div v-if="showCreateAccountModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||
<div class="modal-content w-full max-w-2xl p-8 mx-auto max-h-[90vh] overflow-y-auto">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-green-500 to-green-600 rounded-xl flex items-center justify-center">
|
||
<i class="fas fa-user-circle text-white"></i>
|
||
</div>
|
||
<h3 class="text-xl font-bold text-gray-900">添加 Claude 账户</h3>
|
||
</div>
|
||
<button
|
||
@click="closeCreateAccountModal"
|
||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<i class="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 步骤指示器 -->
|
||
<div class="flex items-center justify-center mb-8">
|
||
<div class="flex items-center space-x-4">
|
||
<div class="flex items-center">
|
||
<div :class="['w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold',
|
||
oauthStep >= 1 ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-500']">
|
||
1
|
||
</div>
|
||
<span class="ml-2 text-sm font-medium text-gray-700">基本信息</span>
|
||
</div>
|
||
<div class="w-8 h-0.5 bg-gray-300"></div>
|
||
<div class="flex items-center">
|
||
<div :class="['w-8 h-8 rounded-full flex items-center justify-center text-sm font-semibold',
|
||
oauthStep >= 2 ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-500']">
|
||
2
|
||
</div>
|
||
<span class="ml-2 text-sm font-medium text-gray-700">授权认证</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 步骤1: 基本信息和代理设置 -->
|
||
<div v-if="oauthStep === 1">
|
||
<div class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">添加方式</label>
|
||
<div class="flex gap-4">
|
||
<label class="flex items-center cursor-pointer">
|
||
<input
|
||
type="radio"
|
||
v-model="accountForm.addType"
|
||
value="oauth"
|
||
class="mr-2"
|
||
>
|
||
<span class="text-sm text-gray-700">OAuth 授权 (推荐)</span>
|
||
</label>
|
||
<label class="flex items-center cursor-pointer">
|
||
<input
|
||
type="radio"
|
||
v-model="accountForm.addType"
|
||
value="manual"
|
||
class="mr-2"
|
||
>
|
||
<span class="text-sm text-gray-700">手动输入 Access Token</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">账户名称</label>
|
||
<input
|
||
v-model="accountForm.name"
|
||
type="text"
|
||
required
|
||
class="form-input w-full"
|
||
placeholder="为账户设置一个易识别的名称"
|
||
>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">描述 (可选)</label>
|
||
<textarea
|
||
v-model="accountForm.description"
|
||
rows="3"
|
||
class="form-input w-full resize-none"
|
||
placeholder="账户用途说明..."
|
||
></textarea>
|
||
</div>
|
||
|
||
<!-- 手动输入 Token 字段 -->
|
||
<div v-if="accountForm.addType === 'manual'" class="space-y-4 bg-blue-50 p-4 rounded-lg border border-blue-200">
|
||
<div class="flex items-start gap-3 mb-4">
|
||
<div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
|
||
<i class="fas fa-info text-white text-sm"></i>
|
||
</div>
|
||
<div>
|
||
<h5 class="font-semibold text-blue-900 mb-2">手动输入 Token</h5>
|
||
<p class="text-sm text-blue-800 mb-2">请输入有效的 Claude Access Token。如果您有 Refresh Token,建议也一并填写以支持自动刷新。</p>
|
||
<p class="text-xs text-blue-600">💡 如果未填写 Refresh Token,Token 过期后需要手动更新。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Access Token *</label>
|
||
<textarea
|
||
v-model="accountForm.accessToken"
|
||
rows="4"
|
||
class="form-input w-full resize-none font-mono text-sm"
|
||
placeholder="sk-ant-oat01-..."
|
||
required
|
||
></textarea>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Refresh Token (可选)</label>
|
||
<textarea
|
||
v-model="accountForm.refreshToken"
|
||
rows="4"
|
||
class="form-input w-full resize-none font-mono text-sm"
|
||
placeholder="sk-ant-ort01-..."
|
||
></textarea>
|
||
<p class="text-xs text-gray-500 mt-2">如果有 Refresh Token,填写后系统可以自动刷新过期的 Access Token</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="border-t pt-6">
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">代理设置 (可选)</label>
|
||
<p class="text-sm text-gray-500 mb-4">如果需要使用代理访问Claude服务,请配置代理信息。OAuth授权也将通过此代理进行。</p>
|
||
<select
|
||
v-model="accountForm.proxyType"
|
||
class="form-input w-full"
|
||
>
|
||
<option value="">不使用代理</option>
|
||
<option value="socks5">SOCKS5 代理</option>
|
||
<option value="http">HTTP 代理</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div v-if="accountForm.proxyType" class="space-y-4 pl-4 border-l-2 border-blue-200">
|
||
<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="accountForm.proxyHost"
|
||
type="text"
|
||
class="form-input w-full"
|
||
placeholder="127.0.0.1"
|
||
>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">代理端口</label>
|
||
<input
|
||
v-model="accountForm.proxyPort"
|
||
type="number"
|
||
class="form-input w-full"
|
||
placeholder="1080"
|
||
>
|
||
</div>
|
||
</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="accountForm.proxyUsername"
|
||
type="text"
|
||
class="form-input w-full"
|
||
placeholder="代理用户名"
|
||
>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">密码 (可选)</label>
|
||
<input
|
||
v-model="accountForm.proxyPassword"
|
||
type="password"
|
||
class="form-input w-full"
|
||
placeholder="代理密码"
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-3 pt-6">
|
||
<button
|
||
type="button"
|
||
@click="closeCreateAccountModal()"
|
||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
v-if="accountForm.addType === 'oauth'"
|
||
type="button"
|
||
@click="nextOAuthStep()"
|
||
:disabled="!accountForm.name"
|
||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
下一步 <i class="fas fa-arrow-right ml-2"></i>
|
||
</button>
|
||
<button
|
||
v-if="accountForm.addType === 'manual'"
|
||
type="button"
|
||
@click="createManualAccount()"
|
||
:disabled="!accountForm.name || !accountForm.accessToken || createAccountLoading"
|
||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
<div v-if="createAccountLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-check mr-2"></i>
|
||
{{ createAccountLoading ? '创建中...' : '创建账户' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 步骤2: OAuth 授权 -->
|
||
<div v-if="oauthStep === 2">
|
||
<div class="space-y-6">
|
||
<!-- 获取授权URL -->
|
||
<div v-if="!oauthData.authUrl" class="text-center py-8">
|
||
<div class="w-16 h-16 mx-auto mb-4 bg-blue-100 rounded-full flex items-center justify-center">
|
||
<i class="fas fa-link text-blue-600 text-2xl"></i>
|
||
</div>
|
||
<h5 class="text-lg font-semibold text-gray-900 mb-2">获取授权链接</h5>
|
||
<p class="text-gray-600 mb-6">点击下方按钮生成OAuth授权链接</p>
|
||
<button
|
||
@click="generateAuthUrl()"
|
||
:disabled="authUrlLoading"
|
||
class="btn btn-primary px-8 py-3 font-semibold"
|
||
>
|
||
<div v-if="authUrlLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-magic mr-2"></i>
|
||
{{ authUrlLoading ? '生成中...' : '生成授权链接' }}
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 显示授权URL和输入框 -->
|
||
<div v-if="oauthData.authUrl">
|
||
<div class="bg-blue-50 border border-blue-200 rounded-xl p-6 mb-6">
|
||
<div class="flex items-start gap-3 mb-4">
|
||
<div class="w-8 h-8 bg-blue-500 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
|
||
<i class="fas fa-info text-white text-sm"></i>
|
||
</div>
|
||
<div>
|
||
<h5 class="font-semibold text-blue-900 mb-2">操作说明</h5>
|
||
<ol class="text-sm text-blue-800 space-y-1 list-decimal list-inside">
|
||
<li>点击下方的授权链接,在新页面中完成Claude Code登录</li>
|
||
<li>点击"授权"按钮同意应用权限</li>
|
||
<li>页面会显示一个 <strong>Authorization Code</strong></li>
|
||
<li>复制这个 Authorization Code 并粘贴到下方输入框</li>
|
||
<li class="text-xs text-blue-600">💡 提示: 请直接粘贴显示的Authorization Code</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">授权链接</label>
|
||
<div class="flex gap-2">
|
||
<input
|
||
:value="oauthData.authUrl"
|
||
readonly
|
||
class="form-input flex-1 font-mono text-sm bg-gray-50"
|
||
>
|
||
<button
|
||
@click="copyToClipboard(oauthData.authUrl)"
|
||
class="btn btn-primary px-4 py-2 flex items-center gap-2"
|
||
>
|
||
<i class="fas fa-copy"></i>复制
|
||
</button>
|
||
<a
|
||
:href="oauthData.authUrl"
|
||
target="_blank"
|
||
class="btn btn-success px-4 py-2 flex items-center gap-2"
|
||
>
|
||
<i class="fas fa-external-link-alt"></i>打开
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">
|
||
<i class="fas fa-key text-blue-500 mr-2"></i>Authorization Code
|
||
</label>
|
||
<textarea
|
||
v-model="oauthData.callbackUrl"
|
||
rows="3"
|
||
class="form-input w-full resize-none font-mono text-sm"
|
||
placeholder="粘贴从Claude页面获取的Authorization Code..."
|
||
></textarea>
|
||
<p class="text-xs text-gray-500 mt-2">
|
||
<i class="fas fa-info-circle mr-1"></i>
|
||
请粘贴从Claude页面复制的Authorization Code
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-3 pt-6">
|
||
<button
|
||
type="button"
|
||
@click="oauthStep = 1"
|
||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
<i class="fas fa-arrow-left mr-2"></i>上一步
|
||
</button>
|
||
<button
|
||
type="button"
|
||
@click="createOAuthAccount()"
|
||
:disabled="!oauthData.callbackUrl || !oauthData.authUrl || createAccountLoading"
|
||
class="btn btn-success flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
<div v-if="createAccountLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-check mr-2"></i>
|
||
{{ createAccountLoading ? '创建中...' : '完成创建' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 编辑 Claude 账户模态框 -->
|
||
<div v-if="showEditAccountModal" class="fixed inset-0 modal z-50 flex items-center justify-center p-4">
|
||
<div class="modal-content w-full max-w-2xl p-8 mx-auto max-h-[90vh] overflow-y-auto">
|
||
<div class="flex items-center justify-between mb-6">
|
||
<div class="flex items-center gap-3">
|
||
<div class="w-10 h-10 bg-gradient-to-br from-blue-500 to-blue-600 rounded-xl flex items-center justify-center">
|
||
<i class="fas fa-edit text-white"></i>
|
||
</div>
|
||
<h3 class="text-xl font-bold text-gray-900">编辑 Claude 账户</h3>
|
||
</div>
|
||
<button
|
||
@click="closeEditAccountModal"
|
||
class="text-gray-400 hover:text-gray-600 transition-colors"
|
||
>
|
||
<i class="fas fa-times text-xl"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="space-y-6">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">账户名称</label>
|
||
<input
|
||
v-model="editAccountForm.name"
|
||
type="text"
|
||
required
|
||
class="form-input w-full"
|
||
placeholder="为账户设置一个易识别的名称"
|
||
>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">描述 (可选)</label>
|
||
<textarea
|
||
v-model="editAccountForm.description"
|
||
rows="3"
|
||
class="form-input w-full resize-none"
|
||
placeholder="账户用途说明..."
|
||
></textarea>
|
||
</div>
|
||
|
||
<!-- Token 更新区域 -->
|
||
<div class="bg-amber-50 p-4 rounded-lg border border-amber-200">
|
||
<div class="flex items-start gap-3 mb-4">
|
||
<div class="w-8 h-8 bg-amber-500 rounded-lg flex items-center justify-center flex-shrink-0 mt-1">
|
||
<i class="fas fa-key text-white text-sm"></i>
|
||
</div>
|
||
<div>
|
||
<h5 class="font-semibold text-amber-900 mb-2">更新 Token</h5>
|
||
<p class="text-sm text-amber-800 mb-2">可以更新 Access Token 和 Refresh Token。为了安全起见,不会显示当前的 Token 值。</p>
|
||
<p class="text-xs text-amber-600">💡 留空表示不更新该字段。</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Access Token</label>
|
||
<textarea
|
||
v-model="editAccountForm.accessToken"
|
||
rows="4"
|
||
class="form-input w-full resize-none font-mono text-sm"
|
||
placeholder="留空表示不更新,否则输入新的 Access Token"
|
||
></textarea>
|
||
</div>
|
||
|
||
<div>
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">Refresh Token</label>
|
||
<textarea
|
||
v-model="editAccountForm.refreshToken"
|
||
rows="4"
|
||
class="form-input w-full resize-none font-mono text-sm"
|
||
placeholder="留空表示不更新,否则输入新的 Refresh Token"
|
||
></textarea>
|
||
<p class="text-xs text-gray-500 mt-2">如果有 Refresh Token,填写后系统可以自动刷新过期的 Access Token</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 代理设置 -->
|
||
<div class="border-t pt-6">
|
||
<label class="block text-sm font-semibold text-gray-700 mb-3">代理设置 (可选)</label>
|
||
<p class="text-sm text-gray-500 mb-4">如果需要修改代理设置,请更新代理信息。</p>
|
||
<select
|
||
v-model="editAccountForm.proxyType"
|
||
class="form-input w-full"
|
||
>
|
||
<option value="">不使用代理</option>
|
||
<option value="socks5">SOCKS5 代理</option>
|
||
<option value="http">HTTP 代理</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div v-if="editAccountForm.proxyType" class="space-y-4 pl-4 border-l-2 border-blue-200">
|
||
<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="editAccountForm.proxyHost"
|
||
type="text"
|
||
class="form-input w-full"
|
||
placeholder="127.0.0.1"
|
||
>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">代理端口</label>
|
||
<input
|
||
v-model="editAccountForm.proxyPort"
|
||
type="number"
|
||
class="form-input w-full"
|
||
placeholder="1080"
|
||
>
|
||
</div>
|
||
</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="editAccountForm.proxyUsername"
|
||
type="text"
|
||
class="form-input w-full"
|
||
placeholder="代理用户名"
|
||
>
|
||
</div>
|
||
<div>
|
||
<label class="block text-sm font-medium text-gray-700 mb-2">密码 (可选)</label>
|
||
<input
|
||
v-model="editAccountForm.proxyPassword"
|
||
type="password"
|
||
class="form-input w-full"
|
||
placeholder="代理密码"
|
||
>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex gap-3 pt-6">
|
||
<button
|
||
type="button"
|
||
@click="closeEditAccountModal"
|
||
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 rounded-xl font-semibold hover:bg-gray-200 transition-colors"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
type="button"
|
||
@click="updateAccount()"
|
||
:disabled="!editAccountForm.name || editAccountLoading"
|
||
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
|
||
>
|
||
<div v-if="editAccountLoading" class="loading-spinner mr-2"></div>
|
||
<i v-else class="fas fa-save mr-2"></i>
|
||
{{ editAccountLoading ? '更新中...' : '保存修改' }}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast 通知组件 -->
|
||
<div v-for="(toast, index) in toasts" :key="toast.id"
|
||
:class="['toast rounded-2xl p-4 shadow-2xl backdrop-blur-sm',
|
||
'toast-' + toast.type,
|
||
toast.show ? 'show' : '']"
|
||
:style="{ top: (80 + index * 80) + 'px' }">
|
||
<div class="flex items-start gap-3">
|
||
<div class="flex-shrink-0 mt-0.5">
|
||
<i :class="getToastIcon(toast.type) + ' text-lg'"></i>
|
||
</div>
|
||
<div class="flex-1 min-w-0">
|
||
<h4 v-if="toast.title" class="font-semibold text-sm mb-1">{{ toast.title }}</h4>
|
||
<p class="text-sm opacity-90 leading-relaxed">{{ toast.message }}</p>
|
||
</div>
|
||
<button @click="removeToast(toast.id)"
|
||
class="flex-shrink-0 text-white/70 hover:text-white transition-colors ml-2">
|
||
<i class="fas fa-times"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/web/app.js"></script>
|
||
</body>
|
||
</html> |