Files
claude-relay-service/web/admin/index.html
shaw 156bfa9b58 feat: 添加管理员账户信息修改功能
- 新增右上角用户菜单,包含修改账户信息和退出登录选项
- 实现修改用户名和密码功能,支持独立修改或同时修改
- 添加密码强度验证(最少8位)和确认密码验证
- 修改后自动退出登录,确保安全性
- 同步更新Redis和data/init.json文件,保持数据一致性
- 优化用户体验,提供实时反馈和错误提示

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-16 17:16:29 +08:00

2539 lines
177 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
<!-- 用户菜单 -->
<div class="relative">
<button
@click="userMenuOpen = !userMenuOpen"
class="btn btn-primary px-4 py-3 flex items-center gap-2 relative"
>
<i class="fas fa-user-circle"></i>
<span>{{ currentUser.username || 'Admin' }}</span>
<i class="fas fa-chevron-down text-xs transition-transform duration-200" :class="{ 'rotate-180': userMenuOpen }"></i>
</button>
<!-- 悬浮菜单 -->
<div
v-if="userMenuOpen"
class="absolute right-0 top-full mt-2 w-48 bg-white rounded-xl shadow-xl border border-gray-200 py-2 z-50"
@click.stop
>
<button
@click="openChangePasswordModal"
class="w-full px-4 py-3 text-left text-gray-700 hover:bg-gray-50 transition-colors flex items-center gap-3"
>
<i class="fas fa-key text-blue-500"></i>
<span>修改账户信息</span>
</button>
<hr class="my-2 border-gray-200">
<button
@click="logout"
class="w-full px-4 py-3 text-left text-red-600 hover:bg-red-50 transition-colors flex items-center gap-3"
>
<i class="fas fa-sign-out-alt"></i>
<span>退出登录</span>
</button>
</div>
</div>
</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>
<!-- 当前并发数 -->
<div class="flex justify-between text-sm">
<span class="text-gray-600">当前并发:</span>
<span :class="['font-medium', key.currentConcurrency > 0 ? 'text-orange-600' : 'text-gray-600']">
{{ key.currentConcurrency || 0 }}
<span v-if="key.concurrencyLimit > 0" class="text-xs text-gray-500">/ {{ key.concurrencyLimit }}</span>
</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>
<div class="bg-white/80 rounded-lg p-3 mt-2 mb-2 border border-blue-300">
<p class="text-sm text-blue-900 font-medium mb-1">
<i class="fas fa-folder-open mr-1"></i>
获取 Access Token 的方法:
</p>
<p class="text-xs text-blue-800">
请从已登录 Claude Code 的机器上获取 <code class="bg-blue-100 px-1 py-0.5 rounded font-mono">~/.claude/.credentials.json</code> 文件中的凭证,
请勿使用 Claude 官网 API Keys 页面的密钥。
</p>
</div>
<p class="text-xs text-blue-600">💡 如果未填写 Refresh TokenToken 过期后需要手动更新。</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>
<!-- 修改账户信息模态框 -->
<div v-if="showChangePasswordModal" 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">修改账户信息</h3>
</div>
<button
@click="closeChangePasswordModal"
class="text-gray-400 hover:text-gray-600 transition-colors"
>
<i class="fas fa-times text-xl"></i>
</button>
</div>
<form @submit.prevent="changePassword" class="space-y-6">
<div>
<label class="block text-sm font-semibold text-gray-700 mb-3">当前用户名</label>
<input
:value="currentUser.username || 'Admin'"
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">新用户名</label>
<input
v-model="changePasswordForm.newUsername"
type="text"
class="form-input w-full"
placeholder="输入新用户名(留空保持不变)"
>
<p class="text-xs text-gray-500 mt-2">留空表示不修改用户名</p>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-3">当前密码</label>
<input
v-model="changePasswordForm.currentPassword"
type="password"
required
class="form-input w-full"
placeholder="请输入当前密码"
>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-3">新密码</label>
<input
v-model="changePasswordForm.newPassword"
type="password"
required
class="form-input w-full"
placeholder="请输入新密码"
>
<p class="text-xs text-gray-500 mt-2">密码长度至少8位</p>
</div>
<div>
<label class="block text-sm font-semibold text-gray-700 mb-3">确认新密码</label>
<input
v-model="changePasswordForm.confirmPassword"
type="password"
required
class="form-input w-full"
placeholder="请再次输入新密码"
>
</div>
<div class="flex gap-3 pt-4">
<button
type="button"
@click="closeChangePasswordModal"
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="changePasswordLoading"
class="btn btn-primary flex-1 py-3 px-6 font-semibold"
>
<div v-if="changePasswordLoading" class="loading-spinner mr-2"></div>
<i v-else class="fas fa-save mr-2"></i>
{{ changePasswordLoading ? '保存中...' : '保存修改' }}
</button>
</div>
</form>
</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>