mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 完成web/admin-spa/src/components/apikeys的国际化并修复语法错误和警告
This commit is contained in:
@@ -60,11 +60,7 @@
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<div class="relative">
|
||||
<el-tooltip
|
||||
:content="t('accounts.refreshTooltip')"
|
||||
effect="dark"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-tooltip :content="t('accounts.refreshTooltip')" effect="dark" placement="bottom">
|
||||
<button
|
||||
class="group relative flex items-center justify-center gap-2 rounded-lg border border-gray-200 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm transition-all duration-200 hover:border-gray-300 hover:shadow-md disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:border-gray-500 sm:w-auto"
|
||||
:disabled="accountsLoading"
|
||||
@@ -110,7 +106,9 @@
|
||||
<i class="fas fa-user-circle text-xl text-gray-400" />
|
||||
</div>
|
||||
<p class="text-lg text-gray-500 dark:text-gray-400">{{ t('accounts.noAccounts') }}</p>
|
||||
<p class="mt-2 text-sm text-gray-400 dark:text-gray-500">{{ t('accounts.noAccountsHint') }}</p>
|
||||
<p class="mt-2 text-sm text-gray-400 dark:text-gray-500">
|
||||
{{ t('accounts.noAccountsHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端表格视图 -->
|
||||
@@ -390,7 +388,9 @@
|
||||
class="flex items-center gap-1.5 rounded-lg border border-gray-200 bg-gradient-to-r from-gray-100 to-gray-200 px-2.5 py-1"
|
||||
>
|
||||
<i class="fas fa-question text-xs text-gray-700" />
|
||||
<span class="text-xs font-semibold text-gray-800">{{ t('accounts.unknown') }}</span>
|
||||
<span class="text-xs font-semibold text-gray-800">{{
|
||||
t('accounts.unknown')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -451,7 +451,11 @@
|
||||
typeof account.rateLimitStatus === 'object' &&
|
||||
account.rateLimitStatus.minutesRemaining > 0
|
||||
"
|
||||
>({{ t('accounts.rateLimitTime', { time: formatRateLimitTime(account.rateLimitStatus.minutesRemaining) }) }})</span
|
||||
>({{
|
||||
t('accounts.rateLimitTime', {
|
||||
time: formatRateLimitTime(account.rateLimitStatus.minutesRemaining)
|
||||
})
|
||||
}})</span
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
@@ -609,7 +613,11 @@
|
||||
v-if="account.sessionWindow.remainingTime > 0"
|
||||
class="font-medium text-indigo-600 dark:text-indigo-400"
|
||||
>
|
||||
{{ t('accounts.remaining', { time: formatRemainingTime(account.sessionWindow.remainingTime) }) }}
|
||||
{{
|
||||
t('accounts.remaining', {
|
||||
time: formatRemainingTime(account.sessionWindow.remainingTime)
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -617,7 +625,9 @@
|
||||
<div v-else-if="account.platform === 'claude-console'" class="space-y-2">
|
||||
<div v-if="Number(account.dailyQuota) > 0">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-600 dark:text-gray-300">{{ t('accounts.quotaProgress') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-300">{{
|
||||
t('accounts.quotaProgress')
|
||||
}}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200">
|
||||
{{ getQuotaUsagePercent(account).toFixed(1) }}%
|
||||
</span>
|
||||
@@ -642,9 +652,9 @@
|
||||
</div>
|
||||
<div class="text-xs text-gray-600 dark:text-gray-400">
|
||||
{{ t('accounts.remainingQuota', { amount: formatRemainingQuota(account) }) }}
|
||||
<span class="ml-2 text-gray-400"
|
||||
>{{ t('accounts.reset', { time: account.quotaResetTime || '00:00' }) }}</span
|
||||
>
|
||||
<span class="ml-2 text-gray-400">{{
|
||||
t('accounts.reset', { time: account.quotaResetTime || '00:00' })
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-sm text-gray-400">
|
||||
@@ -682,7 +692,11 @@
|
||||
: 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200'
|
||||
]"
|
||||
:disabled="account.isResetting"
|
||||
:title="account.isResetting ? t('accounts.resetting') : t('accounts.resetStatusTooltip')"
|
||||
:title="
|
||||
account.isResetting
|
||||
? t('accounts.resetting')
|
||||
: t('accounts.resetStatusTooltip')
|
||||
"
|
||||
@click="resetAccountStatus(account)"
|
||||
>
|
||||
<i :class="['fas fa-redo', account.isResetting ? 'animate-spin' : '']" />
|
||||
@@ -698,11 +712,17 @@
|
||||
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
|
||||
]"
|
||||
:disabled="account.isTogglingSchedulable"
|
||||
:title="account.schedulable ? t('accounts.disableTooltip') : t('accounts.enableTooltip')"
|
||||
:title="
|
||||
account.schedulable
|
||||
? t('accounts.disableTooltip')
|
||||
: t('accounts.enableTooltip')
|
||||
"
|
||||
@click="toggleSchedulable(account)"
|
||||
>
|
||||
<i :class="['fas', account.schedulable ? 'fa-toggle-on' : 'fa-toggle-off']" />
|
||||
<span class="ml-1">{{ account.schedulable ? t('accounts.scheduling') : t('accounts.disabled') }}</span>
|
||||
<span class="ml-1">{{
|
||||
account.schedulable ? t('accounts.scheduling') : t('accounts.disabled')
|
||||
}}</span>
|
||||
</button>
|
||||
<button
|
||||
class="rounded bg-blue-100 px-2.5 py-1 text-xs font-medium text-blue-700 transition-colors hover:bg-blue-200"
|
||||
@@ -799,7 +819,9 @@
|
||||
<!-- 使用统计 -->
|
||||
<div class="mb-3 grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('accounts.dailyUsageLabel') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('accounts.dailyUsageLabel') }}
|
||||
</p>
|
||||
<div class="space-y-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="h-1.5 w-1.5 rounded-full bg-blue-500" />
|
||||
@@ -822,7 +844,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('accounts.sessionWindowLabel') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('accounts.sessionWindowLabel') }}
|
||||
</p>
|
||||
<div v-if="account.usage && account.usage.sessionWindow" class="space-y-1">
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="h-1.5 w-1.5 rounded-full bg-purple-500" />
|
||||
@@ -854,11 +878,10 @@
|
||||
>
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-medium text-gray-600 dark:text-gray-300">{{ t('accounts.sessionWindowLabel') }}</span>
|
||||
<el-tooltip
|
||||
:content="t('accounts.sessionWindowTooltipMobile')"
|
||||
placement="top"
|
||||
>
|
||||
<span class="font-medium text-gray-600 dark:text-gray-300">{{
|
||||
t('accounts.sessionWindowLabel')
|
||||
}}</span>
|
||||
<el-tooltip :content="t('accounts.sessionWindowTooltipMobile')" placement="top">
|
||||
<i
|
||||
class="fas fa-question-circle cursor-help text-xs text-gray-400 hover:text-gray-600"
|
||||
/>
|
||||
@@ -890,7 +913,11 @@
|
||||
v-if="account.sessionWindow.remainingTime > 0"
|
||||
class="font-medium text-indigo-600"
|
||||
>
|
||||
{{ t('accounts.remaining', { time: formatRemainingTime(account.sessionWindow.remainingTime) }) }}
|
||||
{{
|
||||
t('accounts.remaining', {
|
||||
time: formatRemainingTime(account.sessionWindow.remainingTime)
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
<span v-else class="text-gray-500"> {{ t('accounts.ended') }} </span>
|
||||
</div>
|
||||
@@ -898,9 +925,15 @@
|
||||
|
||||
<!-- 最后使用时间 -->
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('accounts.lastUsedLabel') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{
|
||||
t('accounts.lastUsedLabel')
|
||||
}}</span>
|
||||
<span class="text-gray-700 dark:text-gray-200">
|
||||
{{ account.lastUsedAt ? formatRelativeTime(account.lastUsedAt) : t('accounts.neverUsed') }}
|
||||
{{
|
||||
account.lastUsedAt
|
||||
? formatRelativeTime(account.lastUsedAt)
|
||||
: t('accounts.neverUsed')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -917,7 +950,9 @@
|
||||
|
||||
<!-- 调度优先级 -->
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('accounts.priorityLabel') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{
|
||||
t('accounts.priorityLabel')
|
||||
}}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-200">
|
||||
{{ account.priority || 50 }}
|
||||
</span>
|
||||
@@ -1610,10 +1645,7 @@ const deleteAccount = async (account) => {
|
||||
).length
|
||||
|
||||
if (boundKeysCount > 0) {
|
||||
showToast(
|
||||
t('accounts.cannotDeleteBoundAccount', { count: boundKeysCount }),
|
||||
'error'
|
||||
)
|
||||
showToast(t('accounts.cannotDeleteBoundAccount', { count: boundKeysCount }), 'error')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1749,7 +1781,10 @@ const toggleSchedulable = async (account) => {
|
||||
|
||||
if (data.success) {
|
||||
account.schedulable = data.schedulable
|
||||
showToast(data.schedulable ? t('accounts.enabledScheduling') : t('accounts.disabledScheduling'), 'success')
|
||||
showToast(
|
||||
data.schedulable ? t('accounts.enabledScheduling') : t('accounts.disabledScheduling'),
|
||||
'success'
|
||||
)
|
||||
} else {
|
||||
showToast(data.message || t('accounts.operationFailed'), 'error')
|
||||
}
|
||||
|
||||
@@ -68,7 +68,10 @@
|
||||
icon="fa-calendar-alt"
|
||||
icon-color="text-blue-500"
|
||||
:options="timeRangeOptions"
|
||||
:placeholder="timeRangeOptions.find(o => o.value === 'today')?.label || t('apiKeys.timeRange.today')"
|
||||
:placeholder="
|
||||
timeRangeOptions.find((o) => o.value === 'today')?.label ||
|
||||
t('apiKeys.timeRange.today')
|
||||
"
|
||||
@change="loadApiKeys()"
|
||||
/>
|
||||
</div>
|
||||
@@ -84,7 +87,9 @@
|
||||
icon="fa-tags"
|
||||
icon-color="text-purple-500"
|
||||
:options="tagOptions"
|
||||
:placeholder="tagOptions.find(o => o.value === '')?.label || t('apiKeys.allTags')"
|
||||
:placeholder="
|
||||
tagOptions.find((o) => o.value === '')?.label || t('apiKeys.allTags')
|
||||
"
|
||||
@change="currentPage = 1"
|
||||
/>
|
||||
<span
|
||||
@@ -105,7 +110,11 @@
|
||||
<input
|
||||
v-model="searchKeyword"
|
||||
class="w-full rounded-lg border border-gray-200 bg-white px-3 py-2 pl-9 text-sm text-gray-700 placeholder-gray-400 shadow-sm transition-all duration-200 hover:border-gray-300 focus:border-cyan-500 focus:outline-none focus:ring-2 focus:ring-cyan-500/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-200 dark:placeholder-gray-500 dark:hover:border-gray-500"
|
||||
:placeholder="isLdapEnabled ? t('apiKeys.searchPlaceholderWithOwner') : t('apiKeys.searchPlaceholder')"
|
||||
:placeholder="
|
||||
isLdapEnabled
|
||||
? t('apiKeys.searchPlaceholderWithOwner')
|
||||
: t('apiKeys.searchPlaceholder')
|
||||
"
|
||||
type="text"
|
||||
@input="currentPage = 1"
|
||||
/>
|
||||
@@ -148,7 +157,9 @@
|
||||
class="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-blue-500 to-indigo-500 opacity-0 blur transition duration-300 group-hover:opacity-20"
|
||||
></div>
|
||||
<i class="fas fa-edit relative text-blue-600 dark:text-blue-400" />
|
||||
<span class="relative">{{ t('apiKeys.bulkEdit') }} ({{ selectedApiKeys.length }})</span>
|
||||
<span class="relative"
|
||||
>{{ t('apiKeys.bulkEdit') }} ({{ selectedApiKeys.length }})</span
|
||||
>
|
||||
</button>
|
||||
|
||||
<!-- 批量删除按钮 - 移到刷新按钮旁边 -->
|
||||
@@ -161,7 +172,9 @@
|
||||
class="absolute -inset-0.5 rounded-lg bg-gradient-to-r from-red-500 to-pink-500 opacity-0 blur transition duration-300 group-hover:opacity-20"
|
||||
></div>
|
||||
<i class="fas fa-trash relative text-red-600 dark:text-red-400" />
|
||||
<span class="relative">{{ t('apiKeys.bulkDelete') }} ({{ selectedApiKeys.length }})</span>
|
||||
<span class="relative"
|
||||
>{{ t('apiKeys.bulkDelete') }} ({{ selectedApiKeys.length }})</span
|
||||
>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -471,25 +484,34 @@
|
||||
<!-- 今日使用统计 -->
|
||||
<div class="mb-2">
|
||||
<div class="mb-1 flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.dailyRequests') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.dailyRequests')
|
||||
}}</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100"
|
||||
>{{ formatNumber(key.usage?.daily?.requests || 0) }}{{ t('apiKeys.requests') }}</span
|
||||
>{{ formatNumber(key.usage?.daily?.requests || 0)
|
||||
}}{{ t('apiKeys.requests') }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.dailyCost') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.dailyCost')
|
||||
}}</span>
|
||||
<span class="font-semibold text-green-600"
|
||||
>${{ (key.dailyCost || 0).toFixed(4) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.totalCost') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.totalCost')
|
||||
}}</span>
|
||||
<span class="font-semibold text-blue-600"
|
||||
>${{ (key.totalCost || 0).toFixed(4) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.lastUsed') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.lastUsed')
|
||||
}}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300">{{
|
||||
formatLastUsed(key.lastUsedAt)
|
||||
}}</span>
|
||||
@@ -499,7 +521,9 @@
|
||||
<!-- 每日费用限制进度条 -->
|
||||
<div v-if="key.dailyCostLimit > 0" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('apiKeys.dailyLimit') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{
|
||||
t('apiKeys.dailyLimit')
|
||||
}}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">
|
||||
${{ (key.dailyCost || 0).toFixed(2) }} / ${{
|
||||
key.dailyCostLimit.toFixed(2)
|
||||
@@ -518,7 +542,9 @@
|
||||
<!-- Opus 周费用限制进度条 -->
|
||||
<div v-if="key.weeklyOpusCostLimit > 0" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('apiKeys.weeklyOpusLimit') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{
|
||||
t('apiKeys.weeklyOpusLimit')
|
||||
}}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">
|
||||
${{ (key.weeklyOpusCost || 0).toFixed(2) }} / ${{
|
||||
key.weeklyOpusCostLimit.toFixed(2)
|
||||
@@ -703,7 +729,9 @@
|
||||
<td class="bg-gray-50 px-3 py-4 dark:bg-gray-700" colspan="8">
|
||||
<div v-if="!apiKeyModelStats[key.id]" class="py-4 text-center">
|
||||
<div class="loading-spinner mx-auto" />
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ t('apiKeys.loadingModelStats') }}</p>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.loadingModelStats') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
<!-- 通用的标题和时间筛选器,无论是否有数据都显示 -->
|
||||
@@ -719,7 +747,11 @@
|
||||
v-if="apiKeyModelStats[key.id] && apiKeyModelStats[key.id].length > 0"
|
||||
class="rounded-full bg-gray-100 px-2 py-1 text-xs text-gray-500 dark:bg-gray-700 dark:text-gray-400"
|
||||
>
|
||||
{{ t('apiKeys.modelStatsCount', { count: apiKeyModelStats[key.id].length }) }}
|
||||
{{
|
||||
t('apiKeys.modelStatsCount', {
|
||||
count: apiKeyModelStats[key.id].length
|
||||
})
|
||||
}}
|
||||
</span>
|
||||
|
||||
<!-- API Keys日期筛选器 -->
|
||||
@@ -773,7 +805,9 @@
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-center gap-2">
|
||||
<i class="fas fa-chart-line text-lg text-gray-400" />
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('apiKeys.noModelData') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.noModelData') }}
|
||||
</p>
|
||||
<button
|
||||
class="ml-2 flex items-center gap-1 text-sm text-blue-500 transition-colors hover:text-blue-700"
|
||||
:title="t('apiKeys.resetFilter')"
|
||||
@@ -1078,7 +1112,9 @@
|
||||
<!-- 今日使用 -->
|
||||
<div class="rounded-lg bg-gray-50 p-3 dark:bg-gray-700">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{{ t('apiKeys.dailyUsage') }}</span>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.dailyUsage')
|
||||
}}</span>
|
||||
<button
|
||||
class="text-xs text-blue-600 hover:text-blue-800"
|
||||
@click="showUsageDetails(key)"
|
||||
@@ -1089,19 +1125,26 @@
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ formatNumber(key.usage?.daily?.requests || 0) }} {{ t('apiKeys.requests') }}
|
||||
{{ formatNumber(key.usage?.daily?.requests || 0) }}
|
||||
{{ t('apiKeys.requests') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.requests') }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('apiKeys.requests') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm font-semibold text-green-600">
|
||||
${{ (key.dailyCost || 0).toFixed(4) }}
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ t('apiKeys.totalCost') }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('apiKeys.totalCost') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flex items-center justify-between">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{{ t('apiKeys.lastUsed') }}</span>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.lastUsed')
|
||||
}}</span>
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
formatLastUsed(key.lastUsedAt)
|
||||
}}</span>
|
||||
@@ -1111,7 +1154,9 @@
|
||||
<!-- 限制进度 -->
|
||||
<div v-if="key.dailyCostLimit > 0" class="space-y-1">
|
||||
<div class="flex items-center justify-between text-xs">
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ t('apiKeys.dailyLimit') }}</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{
|
||||
t('apiKeys.dailyLimit')
|
||||
}}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300">
|
||||
${{ (key.dailyCost || 0).toFixed(2) }} / ${{ key.dailyCostLimit.toFixed(2) }}
|
||||
</span>
|
||||
@@ -1250,7 +1295,9 @@
|
||||
{{ t('apiKeys.totalRecords', { count: sortedApiKeys.length }) }}
|
||||
</span>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 sm:text-sm">{{ t('apiKeys.pageSize') }}</span>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 sm:text-sm">{{
|
||||
t('apiKeys.pageSize')
|
||||
}}</span>
|
||||
<select
|
||||
v-model="pageSize"
|
||||
class="rounded-md border border-gray-200 bg-white px-2 py-1 text-xs text-gray-700 transition-colors hover:border-gray-300 focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:border-gray-500 sm:text-sm"
|
||||
@@ -1260,7 +1307,9 @@
|
||||
{{ size }}
|
||||
</option>
|
||||
</select>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 sm:text-sm">{{ t('apiKeys.records') }}</span>
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400 sm:text-sm">{{
|
||||
t('apiKeys.records')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1475,19 +1524,26 @@
|
||||
<td class="px-3 py-4">
|
||||
<div class="text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.requests') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.requests')
|
||||
}}</span>
|
||||
<span class="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ formatNumber(key.usage?.total?.requests || 0) }} {{ t('apiKeys.requests') }}
|
||||
{{ formatNumber(key.usage?.total?.requests || 0) }}
|
||||
{{ t('apiKeys.requests') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.totalCost') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.totalCost')
|
||||
}}</span>
|
||||
<span class="font-semibold text-green-600">
|
||||
${{ (key.usage?.total?.cost || 0).toFixed(4) }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-if="key.lastUsedAt" class="flex items-center justify-between">
|
||||
<span class="text-gray-600 dark:text-gray-400">{{ t('apiKeys.lastUsed') }}</span>
|
||||
<span class="text-gray-600 dark:text-gray-400">{{
|
||||
t('apiKeys.lastUsed')
|
||||
}}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300">
|
||||
{{ formatLastUsed(key.lastUsedAt) }}
|
||||
</span>
|
||||
@@ -2350,9 +2406,7 @@ const toggleApiKeyStatus = async (key) => {
|
||||
)
|
||||
} else {
|
||||
// 降级方案
|
||||
confirmed = confirm(
|
||||
t('apiKeys.confirmDisable', { name: key.name })
|
||||
)
|
||||
confirmed = confirm(t('apiKeys.confirmDisable', { name: key.name }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2539,7 +2593,12 @@ const batchDeleteApiKeys = async () => {
|
||||
const message = t('apiKeys.confirmBatchDelete', { count: selectedCount })
|
||||
|
||||
if (window.showConfirm) {
|
||||
confirmed = await window.showConfirm(t('apiKeys.confirmBatchDelete').split(' ')[0], message, t('common.confirm'), t('common.cancel'))
|
||||
confirmed = await window.showConfirm(
|
||||
t('apiKeys.confirmBatchDelete').split(' ')[0],
|
||||
message,
|
||||
t('common.confirm'),
|
||||
t('common.cancel')
|
||||
)
|
||||
} else {
|
||||
confirmed = confirm(message)
|
||||
}
|
||||
@@ -2562,7 +2621,10 @@ const batchDeleteApiKeys = async () => {
|
||||
// 如果有失败的,显示详细信息
|
||||
if (failedCount > 0) {
|
||||
const errorMessages = errors.map((e) => `${e.keyId}: ${e.error}`).join('\n')
|
||||
showToast(t('apiKeys.batchPartialFail', { failed: failedCount }) + ':\n' + errorMessages, 'warning')
|
||||
showToast(
|
||||
t('apiKeys.batchPartialFail', { failed: failedCount }) + ':\n' + errorMessages,
|
||||
'warning'
|
||||
)
|
||||
}
|
||||
} else {
|
||||
showToast(t('apiKeys.batchAllFailed'), 'error')
|
||||
|
||||
@@ -33,7 +33,9 @@
|
||||
to="/user-login"
|
||||
>
|
||||
<i class="fas fa-user text-sm md:text-base" />
|
||||
<span class="text-xs font-semibold tracking-wide md:text-sm">{{ t('apiStats.userLogin') }}</span>
|
||||
<span class="text-xs font-semibold tracking-wide md:text-sm">{{
|
||||
t('apiStats.userLogin')
|
||||
}}</span>
|
||||
</router-link>
|
||||
<!-- 管理后台按钮 -->
|
||||
<router-link
|
||||
@@ -42,7 +44,9 @@
|
||||
to="/dashboard"
|
||||
>
|
||||
<i class="fas fa-shield-alt text-sm md:text-base" />
|
||||
<span class="text-xs font-semibold tracking-wide md:text-sm">{{ t('apiStats.adminPanel') }}</span>
|
||||
<span class="text-xs font-semibold tracking-wide md:text-sm">{{
|
||||
t('apiStats.adminPanel')
|
||||
}}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,9 +101,9 @@
|
||||
>
|
||||
<div class="flex items-center gap-2 md:gap-3">
|
||||
<i class="fas fa-clock text-base text-blue-500 md:text-lg" />
|
||||
<span class="text-base font-medium text-gray-700 dark:text-gray-200 md:text-lg"
|
||||
>{{ t('apiStats.timeRange') }}</span
|
||||
>
|
||||
<span class="text-base font-medium text-gray-700 dark:text-gray-200 md:text-lg">{{
|
||||
t('apiStats.timeRange')
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="flex w-full gap-2 md:w-auto">
|
||||
<button
|
||||
@@ -190,7 +194,7 @@ const currentTutorialComponent = computed(() => {
|
||||
const components = {
|
||||
'zh-cn': TutorialViewZhCn,
|
||||
'zh-tw': TutorialViewZhTw,
|
||||
'en': TutorialViewEn
|
||||
en: TutorialViewEn
|
||||
}
|
||||
return components[locale] || TutorialViewZhCn
|
||||
})
|
||||
|
||||
@@ -42,7 +42,12 @@
|
||||
dashboardData.accountsByPlatform.claude.total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.claudeAccount', { total: dashboardData.accountsByPlatform.claude.total, normal: dashboardData.accountsByPlatform.claude.normal })"
|
||||
:title="
|
||||
t('dashboard.claudeAccount', {
|
||||
total: dashboardData.accountsByPlatform.claude.total,
|
||||
normal: dashboardData.accountsByPlatform.claude.normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fas fa-brain text-xs text-indigo-600" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -56,7 +61,12 @@
|
||||
dashboardData.accountsByPlatform['claude-console'].total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.consoleAccount', { total: dashboardData.accountsByPlatform['claude-console'].total, normal: dashboardData.accountsByPlatform['claude-console'].normal })"
|
||||
:title="
|
||||
t('dashboard.consoleAccount', {
|
||||
total: dashboardData.accountsByPlatform['claude-console'].total,
|
||||
normal: dashboardData.accountsByPlatform['claude-console'].normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fas fa-terminal text-xs text-purple-600" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -70,7 +80,12 @@
|
||||
dashboardData.accountsByPlatform.gemini.total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.geminiAccount', { total: dashboardData.accountsByPlatform.gemini.total, normal: dashboardData.accountsByPlatform.gemini.normal })"
|
||||
:title="
|
||||
t('dashboard.geminiAccount', {
|
||||
total: dashboardData.accountsByPlatform.gemini.total,
|
||||
normal: dashboardData.accountsByPlatform.gemini.normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fas fa-robot text-xs text-yellow-600" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -84,7 +99,12 @@
|
||||
dashboardData.accountsByPlatform.bedrock.total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.bedrockAccount', { total: dashboardData.accountsByPlatform.bedrock.total, normal: dashboardData.accountsByPlatform.bedrock.normal })"
|
||||
:title="
|
||||
t('dashboard.bedrockAccount', {
|
||||
total: dashboardData.accountsByPlatform.bedrock.total,
|
||||
normal: dashboardData.accountsByPlatform.bedrock.normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fab fa-aws text-xs text-orange-600" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -98,7 +118,12 @@
|
||||
dashboardData.accountsByPlatform.openai.total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.openaiAccount', { total: dashboardData.accountsByPlatform.openai.total, normal: dashboardData.accountsByPlatform.openai.normal })"
|
||||
:title="
|
||||
t('dashboard.openaiAccount', {
|
||||
total: dashboardData.accountsByPlatform.openai.total,
|
||||
normal: dashboardData.accountsByPlatform.openai.normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fas fa-openai text-xs text-gray-100" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -112,7 +137,12 @@
|
||||
dashboardData.accountsByPlatform.azure_openai.total > 0
|
||||
"
|
||||
class="inline-flex items-center gap-0.5"
|
||||
:title="t('dashboard.azureOpenaiAccount', { total: dashboardData.accountsByPlatform.azure_openai.total, normal: dashboardData.accountsByPlatform.azure_openai.normal })"
|
||||
:title="
|
||||
t('dashboard.azureOpenaiAccount', {
|
||||
total: dashboardData.accountsByPlatform.azure_openai.total,
|
||||
normal: dashboardData.accountsByPlatform.azure_openai.normal
|
||||
})
|
||||
"
|
||||
>
|
||||
<i class="fab fa-microsoft text-xs text-blue-600" />
|
||||
<span class="text-xs font-medium text-gray-700 dark:text-gray-300">{{
|
||||
@@ -153,7 +183,8 @@
|
||||
{{ dashboardData.todayRequests }}
|
||||
</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('dashboard.totalRequests') }}: {{ formatNumber(dashboardData.totalRequests || 0) }}
|
||||
{{ t('dashboard.totalRequests') }}:
|
||||
{{ formatNumber(dashboardData.totalRequests || 0) }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="stat-icon flex-shrink-0 bg-gradient-to-br from-purple-500 to-purple-600">
|
||||
@@ -303,7 +334,9 @@
|
||||
<div>
|
||||
<p class="mb-1 text-xs font-semibold text-gray-600 dark:text-gray-400 sm:text-sm">
|
||||
{{ t('dashboard.realtimeRPM') }}
|
||||
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}{{ t('dashboard.minutes') }})</span>
|
||||
<span class="text-xs text-gray-400"
|
||||
>({{ dashboardData.metricsWindow }}{{ t('dashboard.minutes') }})</span
|
||||
>
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-orange-600 sm:text-3xl">
|
||||
{{ dashboardData.realtimeRPM || 0 }}
|
||||
@@ -326,7 +359,9 @@
|
||||
<div>
|
||||
<p class="mb-1 text-xs font-semibold text-gray-600 dark:text-gray-400 sm:text-sm">
|
||||
{{ t('dashboard.realtimeTPM') }}
|
||||
<span class="text-xs text-gray-400">({{ dashboardData.metricsWindow }}{{ t('dashboard.minutes') }})</span>
|
||||
<span class="text-xs text-gray-400"
|
||||
>({{ dashboardData.metricsWindow }}{{ t('dashboard.minutes') }})</span
|
||||
>
|
||||
</p>
|
||||
<p class="text-2xl font-bold text-rose-600 sm:text-3xl">
|
||||
{{ formatNumber(dashboardData.realtimeTPM || 0) }}
|
||||
@@ -453,7 +488,9 @@
|
||||
@click="refreshAllData()"
|
||||
>
|
||||
<i :class="['fas fa-sync-alt text-xs', { 'animate-spin': isRefreshing }]" />
|
||||
<span class="hidden sm:inline">{{ isRefreshing ? t('dashboard.refreshing') : t('dashboard.refresh') }}</span>
|
||||
<span class="hidden sm:inline">{{
|
||||
isRefreshing ? t('dashboard.refreshing') : t('dashboard.refresh')
|
||||
}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -579,7 +616,9 @@
|
||||
]"
|
||||
@click="((apiKeysTrendMetric = 'requests'), updateApiKeysUsageTrendChart())"
|
||||
>
|
||||
<i class="fas fa-exchange-alt mr-1" /><span class="hidden sm:inline">{{ t('dashboard.requestsCount') }}</span
|
||||
<i class="fas fa-exchange-alt mr-1" /><span class="hidden sm:inline">{{
|
||||
t('dashboard.requestsCount')
|
||||
}}</span
|
||||
><span class="sm:hidden">{{ t('dashboard.requestsCount').split(' ')[0] }}</span>
|
||||
</button>
|
||||
<button
|
||||
@@ -591,7 +630,9 @@
|
||||
]"
|
||||
@click="((apiKeysTrendMetric = 'tokens'), updateApiKeysUsageTrendChart())"
|
||||
>
|
||||
<i class="fas fa-coins mr-1" /><span class="hidden sm:inline">{{ t('dashboard.tokenCount') }}</span
|
||||
<i class="fas fa-coins mr-1" /><span class="hidden sm:inline">{{
|
||||
t('dashboard.tokenCount')
|
||||
}}</span
|
||||
><span class="sm:hidden">Token</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -600,7 +641,9 @@
|
||||
<span v-if="apiKeysTrendData.totalApiKeys > 10">
|
||||
{{ t('dashboard.showingTop10', { count: apiKeysTrendData.totalApiKeys }) }}
|
||||
</span>
|
||||
<span v-else>{{ t('dashboard.totalApiKeysCount', { count: apiKeysTrendData.totalApiKeys }) }}</span>
|
||||
<span v-else>{{
|
||||
t('dashboard.totalApiKeysCount', { count: apiKeysTrendData.totalApiKeys })
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="sm:h-[350px]" style="height: 300px">
|
||||
<canvas ref="apiKeysUsageTrendChart" />
|
||||
@@ -1164,7 +1207,10 @@ function createApiKeysUsageTrendChart() {
|
||||
beginAtZero: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: apiKeysTrendMetric.value === 'tokens' ? t('dashboard.tokenQuantity') : t('dashboard.requestsQuantity'),
|
||||
text:
|
||||
apiKeysTrendMetric.value === 'tokens'
|
||||
? t('dashboard.tokenQuantity')
|
||||
: t('dashboard.requestsQuantity'),
|
||||
color: chartColors.value.text
|
||||
},
|
||||
ticks: {
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
|
||||
<form class="space-y-4 sm:space-y-6" @submit.prevent="handleLogin">
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||
<label
|
||||
class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||
>{{ t('login.username') }}</label
|
||||
>
|
||||
<input
|
||||
@@ -55,7 +56,8 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||
<label
|
||||
class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||
>{{ t('login.password') }}</label
|
||||
>
|
||||
<input
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
<h3 class="mb-1 text-lg font-bold text-gray-900 dark:text-gray-100 sm:mb-2 sm:text-xl">
|
||||
{{ t('settings.title') }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 sm:text-base">{{ t('settings.description') }}</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 sm:text-base">
|
||||
{{ t('settings.description') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 设置分类导航 -->
|
||||
@@ -66,7 +68,9 @@
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.siteName') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.siteNameDescription') }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.siteNameDescription') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -75,7 +79,7 @@
|
||||
v-model="oemSettings.siteName"
|
||||
class="form-input w-full max-w-md dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
maxlength="100"
|
||||
:placeholder="t('settings.siteNamePlaceholder')"
|
||||
:placeholder="t('settings.siteNamePlaceholder')"
|
||||
type="text"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">
|
||||
@@ -97,7 +101,9 @@
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.siteIcon') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.siteIconDescription') }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.siteIconDescription') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -109,12 +115,14 @@
|
||||
class="inline-flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-700"
|
||||
>
|
||||
<img
|
||||
:alt="t('settings.iconPreview')"
|
||||
:alt="t('settings.iconPreview')"
|
||||
class="h-8 w-8"
|
||||
:src="oemSettings.siteIconData || oemSettings.siteIcon"
|
||||
@error="handleIconError"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{ t('settings.currentIcon') }}</span>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{
|
||||
t('settings.currentIcon')
|
||||
}}</span>
|
||||
<button
|
||||
class="rounded-lg px-3 py-1 font-medium text-red-600 transition-colors hover:bg-red-50 hover:text-red-900"
|
||||
@click="removeIcon"
|
||||
@@ -139,9 +147,9 @@
|
||||
<i class="fas fa-upload mr-2" />
|
||||
{{ t('settings.uploadIcon') }}
|
||||
</button>
|
||||
<span class="ml-3 text-xs text-gray-500 dark:text-gray-400"
|
||||
>{{ t('settings.iconFormats') }}</span
|
||||
>
|
||||
<span class="ml-3 text-xs text-gray-500 dark:text-gray-400">{{
|
||||
t('settings.iconFormats')
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -160,7 +168,9 @@
|
||||
<div class="text-sm font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.adminEntry') }}
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">{{ t('settings.adminEntryDescription') }}</div>
|
||||
<div class="text-xs text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.adminEntryDescription') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -172,7 +182,9 @@
|
||||
class="peer relative h-6 w-11 rounded-full bg-gray-200 after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-gray-300 after:bg-white after:transition-all after:content-[''] peer-checked:bg-blue-600 peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-blue-300 dark:border-gray-600 dark:bg-gray-700 dark:peer-focus:ring-blue-800"
|
||||
></div>
|
||||
<span class="ml-3 text-sm font-medium text-gray-900 dark:text-gray-300">{{
|
||||
hideAdminButton ? t('settings.hideLoginButton') : t('settings.showLoginButton')
|
||||
hideAdminButton
|
||||
? t('settings.hideLoginButton')
|
||||
: t('settings.showLoginButton')
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -213,7 +225,9 @@
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<i class="fas fa-clock mr-1" />
|
||||
{{ t('settings.lastUpdated', { time: formatDateTime(oemSettings.updatedAt) }) }}
|
||||
{{
|
||||
t('settings.lastUpdated', { time: formatDateTime(oemSettings.updatedAt) })
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -233,15 +247,19 @@
|
||||
<i class="fas fa-tag"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.siteNameCard') }}</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('settings.siteNameCardDesc') }}</p>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.siteNameCard') }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.siteNameCardDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
v-model="oemSettings.siteName"
|
||||
class="form-input w-full dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
maxlength="100"
|
||||
:placeholder="t('settings.siteNamePlaceholder')"
|
||||
:placeholder="t('settings.siteNamePlaceholder')"
|
||||
type="text"
|
||||
/>
|
||||
</div>
|
||||
@@ -255,7 +273,9 @@
|
||||
<i class="fas fa-image"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.siteIconCard') }}</h3>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.siteIconCard') }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.siteIconCardDesc') }}
|
||||
</p>
|
||||
@@ -268,12 +288,14 @@
|
||||
class="inline-flex items-center gap-3 rounded-lg bg-gray-50 p-3 dark:bg-gray-700"
|
||||
>
|
||||
<img
|
||||
:alt="t('settings.iconPreview')"
|
||||
:alt="t('settings.iconPreview')"
|
||||
class="h-8 w-8"
|
||||
:src="oemSettings.siteIconData || oemSettings.siteIcon"
|
||||
@error="handleIconError"
|
||||
/>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{ t('settings.currentIcon') }}</span>
|
||||
<span class="text-sm text-gray-600 dark:text-gray-400">{{
|
||||
t('settings.currentIcon')
|
||||
}}</span>
|
||||
<button
|
||||
class="rounded-lg px-3 py-1 font-medium text-red-600 transition-colors hover:bg-red-50 hover:text-red-900"
|
||||
@click="removeIcon"
|
||||
@@ -314,8 +336,12 @@
|
||||
<i class="fas fa-eye-slash"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">{{ t('settings.adminEntryCard') }}</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ t('settings.adminEntryCardDesc') }}</p>
|
||||
<h3 class="text-base font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ t('settings.adminEntryCard') }}
|
||||
</h3>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('settings.adminEntryCardDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
@@ -362,7 +388,9 @@
|
||||
class="text-center text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<i class="fas fa-clock mr-1" />
|
||||
{{ t('settings.lastUpdatedMobile', { time: formatDateTime(oemSettings.updatedAt) }) }}
|
||||
{{
|
||||
t('settings.lastUpdatedMobile', { time: formatDateTime(oemSettings.updatedAt) })
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -402,7 +430,9 @@
|
||||
<div
|
||||
class="mb-6 rounded-lg bg-white/80 p-6 shadow-lg backdrop-blur-sm dark:bg-gray-800/80"
|
||||
>
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">{{ t('settings.notificationTypes') }}</h2>
|
||||
<h2 class="mb-4 text-lg font-semibold text-gray-800 dark:text-gray-200">
|
||||
{{ t('settings.notificationTypes') }}
|
||||
</h2>
|
||||
<div class="space-y-3">
|
||||
<div
|
||||
v-for="(enabled, type) in webhookConfig.notificationTypes"
|
||||
@@ -1207,7 +1237,10 @@ const savePlatform = async () => {
|
||||
}
|
||||
|
||||
if (response.success && isMounted.value) {
|
||||
showToast(editingPlatform.value ? t('settings.platformUpdated') : t('settings.platformAdded'), 'success')
|
||||
showToast(
|
||||
editingPlatform.value ? t('settings.platformUpdated') : t('settings.platformAdded'),
|
||||
'success'
|
||||
)
|
||||
await loadWebhookConfig()
|
||||
closePlatformModal()
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ const currentTutorialComponent = computed(() => {
|
||||
const components = {
|
||||
'zh-cn': TutorialViewZhCn,
|
||||
'zh-tw': TutorialViewZhTw,
|
||||
'en': TutorialViewEn
|
||||
en: TutorialViewEn
|
||||
}
|
||||
return components[locale] || TutorialViewZhCn
|
||||
})
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -72,7 +72,8 @@
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t('user.dashboard.welcome') }}, <span class="font-medium">{{ userStore.userName }}</span>
|
||||
{{ t('user.dashboard.welcome') }},
|
||||
<span class="font-medium">{{ userStore.userName }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 主题切换按钮 -->
|
||||
@@ -94,7 +95,9 @@
|
||||
<!-- Overview Tab -->
|
||||
<div v-if="activeTab === 'overview'" class="space-y-6">
|
||||
<div>
|
||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">{{ t('user.dashboard.title') }}</h1>
|
||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('user.dashboard.title') }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ t('user.dashboard.welcomeMessage') }}
|
||||
</p>
|
||||
@@ -272,25 +275,33 @@
|
||||
<div class="mt-5 border-t border-gray-200 dark:border-gray-700">
|
||||
<dl class="divide-y divide-gray-200 dark:divide-gray-700">
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.username') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.username') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
{{ userProfile?.username }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.displayName') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.displayName') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
{{ userProfile?.displayName || t('user.dashboard.notAvailable') }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.email') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.email') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
{{ userProfile?.email || t('user.dashboard.notAvailable') }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.role') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.role') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
<span
|
||||
class="inline-flex items-center rounded-full bg-blue-100 px-2.5 py-0.5 text-xs font-medium text-blue-800 dark:bg-blue-900 dark:text-blue-200"
|
||||
@@ -300,13 +311,17 @@
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.memberSince') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.memberSince') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
{{ formatDate(userProfile?.createdAt) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="py-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-5">
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">{{ t('user.dashboard.lastLogin') }}</dt>
|
||||
<dt class="text-sm font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.dashboard.lastLogin') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm text-gray-900 dark:text-white sm:col-span-2 sm:mt-0">
|
||||
{{ formatDate(userProfile?.lastLoginAt) || t('user.dashboard.notAvailable') }}
|
||||
</dd>
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
<!-- Header -->
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">{{ t('user.management.title') }}</h1>
|
||||
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">
|
||||
{{ t('user.management.title') }}
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
{{ t('user.management.description') }}
|
||||
</p>
|
||||
@@ -254,7 +256,9 @@
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">{{ t('user.management.loadingUsers') }}</p>
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ t('user.management.loadingUsers') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Users List -->
|
||||
@@ -299,7 +303,9 @@
|
||||
: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
|
||||
]"
|
||||
>
|
||||
{{ user.isActive ? t('user.management.active') : t('user.management.disabled') }}
|
||||
{{
|
||||
user.isActive ? t('user.management.active') : t('user.management.disabled')
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
:class="[
|
||||
@@ -328,8 +334,14 @@
|
||||
v-if="user.totalUsage"
|
||||
class="mt-1 flex items-center space-x-4 text-xs text-gray-400 dark:text-gray-500"
|
||||
>
|
||||
<span>{{ formatNumber(user.totalUsage.requests || 0) }} {{ t('user.management.requests') }}</span>
|
||||
<span>${{ (user.totalUsage.totalCost || 0).toFixed(4) }} {{ t('user.management.totalCostLabel') }}</span>
|
||||
<span
|
||||
>{{ formatNumber(user.totalUsage.requests || 0) }}
|
||||
{{ t('user.management.requests') }}</span
|
||||
>
|
||||
<span
|
||||
>${{ (user.totalUsage.totalCost || 0).toFixed(4) }}
|
||||
{{ t('user.management.totalCostLabel') }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -337,7 +349,7 @@
|
||||
<!-- View Usage Stats -->
|
||||
<button
|
||||
class="inline-flex items-center rounded border border-transparent p-1 text-gray-400 hover:text-blue-600"
|
||||
:title="t('user.management.viewUsageStats')"
|
||||
:title="t('user.management.viewUsageStats')"
|
||||
@click="viewUserStats(user)"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -354,7 +366,7 @@
|
||||
<button
|
||||
class="inline-flex items-center rounded border border-transparent p-1 text-gray-400 hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
:disabled="user.apiKeyCount === 0"
|
||||
:title="t('user.management.disableAllApiKeys')"
|
||||
:title="t('user.management.disableAllApiKeys')"
|
||||
@click="disableUserApiKeys(user)"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -375,7 +387,9 @@
|
||||
? 'text-gray-400 hover:text-red-600'
|
||||
: 'text-gray-400 hover:text-green-600'
|
||||
]"
|
||||
:title="user.isActive ? t('user.management.disableUser') : t('user.management.enableUser')"
|
||||
:title="
|
||||
user.isActive ? t('user.management.disableUser') : t('user.management.enableUser')
|
||||
"
|
||||
@click="toggleUserStatus(user)"
|
||||
>
|
||||
<svg
|
||||
@@ -405,7 +419,7 @@
|
||||
<!-- Change Role -->
|
||||
<button
|
||||
class="inline-flex items-center rounded border border-transparent p-1 text-gray-400 hover:text-purple-600"
|
||||
:title="t('user.management.changeRole')"
|
||||
:title="t('user.management.changeRole')"
|
||||
@click="changeUserRole(user)"
|
||||
>
|
||||
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -437,7 +451,9 @@
|
||||
stroke-width="2"
|
||||
/>
|
||||
</svg>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">{{ t('user.management.noUsersFound') }}</h3>
|
||||
<h3 class="mt-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ t('user.management.noUsersFound') }}
|
||||
</h3>
|
||||
<p class="mt-1 text-sm text-gray-500 dark:text-gray-400">
|
||||
{{
|
||||
searchQuery ? t('user.management.noUsersMatch') : t('user.management.noUsersCreated')
|
||||
@@ -597,7 +613,9 @@ const viewUserStats = (user) => {
|
||||
const toggleUserStatus = (user) => {
|
||||
selectedUser.value = user
|
||||
confirmAction.value = {
|
||||
title: user.isActive ? t('user.management.disableUserTitle') : t('user.management.enableUserTitle'),
|
||||
title: user.isActive
|
||||
? t('user.management.disableUserTitle')
|
||||
: t('user.management.enableUserTitle'),
|
||||
message: user.isActive
|
||||
? t('user.management.disableUserMessage', { username: user.username })
|
||||
: t('user.management.enableUserMessage', { username: user.username }),
|
||||
@@ -614,7 +632,10 @@ const disableUserApiKeys = (user) => {
|
||||
selectedUser.value = user
|
||||
confirmAction.value = {
|
||||
title: t('user.management.disableAllKeysTitle'),
|
||||
message: t('user.management.disableAllKeysMessage', { count: user.apiKeyCount, username: user.username }),
|
||||
message: t('user.management.disableAllKeysMessage', {
|
||||
count: user.apiKeyCount,
|
||||
username: user.username
|
||||
}),
|
||||
confirmText: t('user.management.disableKeys'),
|
||||
confirmClass: 'bg-red-600 hover:bg-red-700',
|
||||
action: 'disableKeys'
|
||||
@@ -642,13 +663,21 @@ const handleConfirmAction = async () => {
|
||||
if (userIndex !== -1) {
|
||||
users.value[userIndex].isActive = !user.isActive
|
||||
}
|
||||
showToast(user.isActive ? t('user.management.userDisabledSuccess') : t('user.management.userEnabledSuccess'), 'success')
|
||||
showToast(
|
||||
user.isActive
|
||||
? t('user.management.userDisabledSuccess')
|
||||
: t('user.management.userEnabledSuccess'),
|
||||
'success'
|
||||
)
|
||||
}
|
||||
} else if (action === 'disableKeys') {
|
||||
const response = await apiClient.post(`/users/${user.id}/disable-keys`)
|
||||
|
||||
if (response.success) {
|
||||
showToast(t('user.management.keysDisabledSuccess', { count: response.disabledCount }), 'success')
|
||||
showToast(
|
||||
t('user.management.keysDisabledSuccess', { count: response.disabledCount }),
|
||||
'success'
|
||||
)
|
||||
await loadUsers() // Refresh to get updated counts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,9 @@
|
||||
>
|
||||
file
|
||||
</li>
|
||||
<li>Follow the installation wizard to complete installation, keep default settings</li>
|
||||
<li>
|
||||
Follow the installation wizard to complete installation, keep default settings
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="mb-3 sm:mb-4">
|
||||
@@ -105,14 +107,18 @@
|
||||
<ul class="space-y-1 text-xs text-blue-700 sm:text-sm sm:text-xs">
|
||||
<li>• Recommend using PowerShell instead of CMD</li>
|
||||
<li>• If you encounter permission issues, try running as administrator</li>
|
||||
<li>• Some antivirus software may flag as false positive, need to add to whitelist</li>
|
||||
<li>
|
||||
• Some antivirus software may flag as false positive, need to add to whitelist
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 text-sm font-medium text-green-800 sm:text-base">Verify Installation Success</h6>
|
||||
<h6 class="mb-2 text-sm font-medium text-green-800 sm:text-base">
|
||||
Verify Installation Success
|
||||
</h6>
|
||||
<p class="mb-2 text-xs text-green-700 sm:mb-3 sm:text-sm">
|
||||
After installation completes, open PowerShell or CMD and enter the following commands:
|
||||
</p>
|
||||
@@ -122,7 +128,9 @@
|
||||
<div class="whitespace-nowrap text-gray-300">node --version</div>
|
||||
<div class="whitespace-nowrap text-gray-300">npm --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-green-700 sm:text-sm">If version numbers are displayed, installation was successful!</p>
|
||||
<p class="mt-2 text-xs text-green-700 sm:text-sm">
|
||||
If version numbers are displayed, installation was successful!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,7 +167,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
This command will download and install the latest version of Claude Code from the official npm repository.
|
||||
This command will download and install the latest version of Claude Code from the
|
||||
official npm repository.
|
||||
</p>
|
||||
|
||||
<div class="mt-4 rounded-lg border border-blue-200 bg-blue-50 p-3 sm:p-4">
|
||||
@@ -173,15 +182,21 @@
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">Verify Claude Code Installation</h6>
|
||||
<p class="mb-3 text-sm text-green-700">After installation completes, enter the following command to check if installation was successful:</p>
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">
|
||||
Verify Claude Code Installation
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-green-700">
|
||||
After installation completes, enter the following command to check if installation was
|
||||
successful:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded bg-gray-900 p-2 font-mono text-xs text-green-400 sm:p-3 sm:text-sm"
|
||||
>
|
||||
<div class="whitespace-nowrap text-gray-300">claude --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-green-700">
|
||||
If version number is displayed, congratulations! Claude Code has been successfully installed.
|
||||
If version number is displayed, congratulations! Claude Code has been successfully
|
||||
installed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -230,7 +245,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-yellow-700">
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys" tab above.
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys"
|
||||
tab above.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -280,9 +296,12 @@
|
||||
|
||||
<!-- Verify Environment Variable Setup -->
|
||||
<div class="mt-6 rounded-lg border border-blue-200 bg-blue-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-blue-800 dark:text-blue-300">Verify Environment Variable Setup</h6>
|
||||
<h6 class="mb-2 font-medium text-blue-800 dark:text-blue-300">
|
||||
Verify Environment Variable Setup
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-blue-700">
|
||||
After setting environment variables, you can verify if they were set successfully with the following commands:
|
||||
After setting environment variables, you can verify if they were set successfully with
|
||||
the following commands:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -320,7 +339,8 @@
|
||||
<div>cr_xxxxxxxxxxxxxxxxxx</div>
|
||||
</div>
|
||||
<p class="text-xs text-blue-700">
|
||||
💡 If output is empty or shows variable name itself, environment variable setup failed, please reconfigure.
|
||||
💡 If output is empty or shows variable name itself, environment variable setup
|
||||
failed, please reconfigure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -416,7 +436,8 @@
|
||||
Configure Codex Environment Variables
|
||||
</h5>
|
||||
<p class="mb-3 text-sm text-gray-700 dark:text-gray-300 sm:mb-4 sm:text-base">
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following environment variables:
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following
|
||||
environment variables:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -528,8 +549,8 @@
|
||||
<ul class="list-inside list-disc space-y-1 text-sm">
|
||||
<li>Run PowerShell as Administrator</li>
|
||||
<li>
|
||||
Or configure npm to use user directory: <code
|
||||
class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Or configure npm to use user directory:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>npm config set prefix %APPDATA%\npm</code
|
||||
>
|
||||
</li>
|
||||
@@ -567,7 +588,8 @@
|
||||
<li>Restart PowerShell or CMD</li>
|
||||
<li>Or log out and log back into Windows</li>
|
||||
<li>
|
||||
Verify settings: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Verify settings:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>echo $env:ANTHROPIC_BASE_URL</code
|
||||
>
|
||||
</li>
|
||||
@@ -605,7 +627,8 @@
|
||||
<div class="mb-4">
|
||||
<p class="mb-3 text-gray-700">Method 1: Using Homebrew (Recommended)</p>
|
||||
<p class="mb-2 text-xs text-gray-600 dark:text-gray-400 sm:text-sm">
|
||||
If you already have Homebrew installed, using it to install Node.js will be more convenient:
|
||||
If you already have Homebrew installed, using it to install Node.js will be more
|
||||
convenient:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded-lg bg-gray-900 p-3 font-mono text-xs text-green-400 sm:p-4 sm:text-sm"
|
||||
@@ -655,15 +678,21 @@
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">Verify Installation Success</h6>
|
||||
<p class="mb-3 text-sm text-green-700">After installation, open Terminal and enter the following commands:</p>
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">
|
||||
Verify Installation Success
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-green-700">
|
||||
After installation, open Terminal and enter the following commands:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded bg-gray-900 p-2 font-mono text-xs text-green-400 sm:p-3 sm:text-sm"
|
||||
>
|
||||
<div class="whitespace-nowrap text-gray-300">node --version</div>
|
||||
<div class="whitespace-nowrap text-gray-300">npm --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-green-700">If version numbers are displayed, the installation was successful!</p>
|
||||
<p class="mt-2 text-sm text-green-700">
|
||||
If version numbers are displayed, the installation was successful!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -699,7 +728,9 @@
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-2 text-sm text-gray-600">If you encounter permission issues, you can use sudo:</p>
|
||||
<p class="mb-2 text-sm text-gray-600">
|
||||
If you encounter permission issues, you can use sudo:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded-lg bg-gray-900 p-3 font-mono text-xs text-green-400 sm:p-4 sm:text-sm"
|
||||
>
|
||||
@@ -711,15 +742,21 @@
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">Verify Claude Code Installation</h6>
|
||||
<p class="mb-3 text-sm text-green-700">After installation completes, enter the following command to check if installation was successful:</p>
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">
|
||||
Verify Claude Code Installation
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-green-700">
|
||||
After installation completes, enter the following command to check if installation was
|
||||
successful:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded bg-gray-900 p-2 font-mono text-xs text-green-400 sm:p-3 sm:text-sm"
|
||||
>
|
||||
<div class="whitespace-nowrap text-gray-300">claude --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-green-700">
|
||||
If version number is displayed, congratulations! Claude Code has been successfully installed.
|
||||
If version number is displayed, congratulations! Claude Code has been successfully
|
||||
installed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -766,7 +803,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-yellow-700">
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys" tab above.
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys"
|
||||
tab above.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -903,7 +941,8 @@
|
||||
Configure Codex Environment Variables
|
||||
</h5>
|
||||
<p class="mb-3 text-sm text-gray-700 dark:text-gray-300 sm:mb-4 sm:text-base">
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following environment variables:
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following
|
||||
environment variables:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -1014,13 +1053,14 @@
|
||||
<p class="mb-2">Try the following solutions:</p>
|
||||
<ul class="list-inside list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
Install with sudo: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Install with sudo:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>sudo npm install -g @anthropic-ai/claude-code</code
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
Or configure npm to use user directory: <code
|
||||
class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Or configure npm to use user directory:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>npm config set prefix ~/.npm-global</code
|
||||
>
|
||||
</li>
|
||||
@@ -1040,7 +1080,8 @@
|
||||
<li>Open "System Preferences" → "Security & Privacy"</li>
|
||||
<li>Click "Allow Anyway" or "Open Anyway"</li>
|
||||
<li>
|
||||
Or run in Terminal: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Or run in Terminal:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>sudo spctl --master-disable</code
|
||||
>
|
||||
</li>
|
||||
@@ -1057,10 +1098,13 @@
|
||||
<div class="px-3 pb-3 text-gray-600 sm:px-4 sm:pb-4">
|
||||
<p class="mb-2">Check the following points:</p>
|
||||
<ul class="list-inside list-disc space-y-1 text-sm">
|
||||
<li>Confirm you modified the correct configuration file (.zshrc or .bash_profile)</li>
|
||||
<li>
|
||||
Confirm you modified the correct configuration file (.zshrc or .bash_profile)
|
||||
</li>
|
||||
<li>Restart Terminal</li>
|
||||
<li>
|
||||
Verify settings: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Verify settings:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>echo $ANTHROPIC_BASE_URL</code
|
||||
>
|
||||
</li>
|
||||
@@ -1127,7 +1171,10 @@
|
||||
<h6 class="mb-2 text-sm font-medium text-orange-800 sm:text-base">Linux Notes</h6>
|
||||
<ul class="space-y-1 text-xs text-orange-700 sm:text-sm">
|
||||
<li>• Some distributions may require additional dependencies</li>
|
||||
<li>• If you encounter permission issues, use <code class="rounded bg-orange-200 px-1">sudo</code></li>
|
||||
<li>
|
||||
• If you encounter permission issues, use
|
||||
<code class="rounded bg-orange-200 px-1">sudo</code>
|
||||
</li>
|
||||
<li>• Ensure your user has write permissions to npm's global directory</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1135,15 +1182,21 @@
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">Verify Installation Success</h6>
|
||||
<p class="mb-3 text-sm text-green-700">After installation, open terminal and enter the following commands:</p>
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">
|
||||
Verify Installation Success
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-green-700">
|
||||
After installation, open terminal and enter the following commands:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded bg-gray-900 p-2 font-mono text-xs text-green-400 sm:p-3 sm:text-sm"
|
||||
>
|
||||
<div class="whitespace-nowrap text-gray-300">node --version</div>
|
||||
<div class="whitespace-nowrap text-gray-300">npm --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-green-700">If version numbers are displayed, the installation was successful!</p>
|
||||
<p class="mt-2 text-sm text-green-700">
|
||||
If version numbers are displayed, the installation was successful!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1179,7 +1232,9 @@
|
||||
npm install -g @anthropic-ai/claude-code
|
||||
</div>
|
||||
</div>
|
||||
<p class="mb-2 text-sm text-gray-600">If you encounter permission issues, you can use sudo:</p>
|
||||
<p class="mb-2 text-sm text-gray-600">
|
||||
If you encounter permission issues, you can use sudo:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded-lg bg-gray-900 p-3 font-mono text-xs text-green-400 sm:p-4 sm:text-sm"
|
||||
>
|
||||
@@ -1191,15 +1246,21 @@
|
||||
|
||||
<!-- Verify Installation -->
|
||||
<div class="rounded-lg border border-green-200 bg-green-50 p-3 sm:p-4">
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">Verify Claude Code Installation</h6>
|
||||
<p class="mb-3 text-sm text-green-700">After installation completes, enter the following command to check if installation was successful:</p>
|
||||
<h6 class="mb-2 font-medium text-green-800 dark:text-green-300">
|
||||
Verify Claude Code Installation
|
||||
</h6>
|
||||
<p class="mb-3 text-sm text-green-700">
|
||||
After installation completes, enter the following command to check if installation was
|
||||
successful:
|
||||
</p>
|
||||
<div
|
||||
class="overflow-x-auto rounded bg-gray-900 p-2 font-mono text-xs text-green-400 sm:p-3 sm:text-sm"
|
||||
>
|
||||
<div class="whitespace-nowrap text-gray-300">claude --version</div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-green-700">
|
||||
If version number is displayed, congratulations! Claude Code has been successfully installed.
|
||||
If version number is displayed, congratulations! Claude Code has been successfully
|
||||
installed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1246,7 +1307,8 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-2 text-xs text-yellow-700">
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys" tab above.
|
||||
💡 Remember to replace "your-api-key" with the actual key created in the "API Keys"
|
||||
tab above.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -1381,7 +1443,8 @@
|
||||
Configure Codex Environment Variables
|
||||
</h5>
|
||||
<p class="mb-3 text-sm text-gray-700 dark:text-gray-300 sm:mb-4 sm:text-base">
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following environment variables:
|
||||
If you use tools that support OpenAI API (such as Codex), you need to set the following
|
||||
environment variables:
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -1492,18 +1555,20 @@
|
||||
<p class="mb-2">Try the following solutions:</p>
|
||||
<ul class="list-inside list-disc space-y-1 text-sm">
|
||||
<li>
|
||||
Install with sudo: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Install with sudo:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>sudo npm install -g @anthropic-ai/claude-code</code
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
Or configure npm to use user directory: <code
|
||||
class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Or configure npm to use user directory:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>npm config set prefix ~/.npm-global</code
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
Then add to PATH: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Then add to PATH:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>export PATH=~/.npm-global/bin:$PATH</code
|
||||
>
|
||||
</li>
|
||||
@@ -1547,7 +1612,8 @@
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm">source ~/.bashrc</code>
|
||||
</li>
|
||||
<li>
|
||||
Verify settings: <code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
Verify settings:
|
||||
<code class="rounded bg-gray-200 px-1 text-xs sm:text-sm"
|
||||
>echo $ANTHROPIC_BASE_URL</code
|
||||
>
|
||||
</li>
|
||||
@@ -1564,10 +1630,12 @@
|
||||
>
|
||||
<h5 class="mb-2 text-lg font-semibold sm:text-xl">🎉 Congratulations!</h5>
|
||||
<p class="mb-3 text-sm text-blue-100 sm:mb-4 sm:text-base">
|
||||
You have successfully installed and configured Claude Code. Now you can start enjoying the convenience brought by AI programming assistant.
|
||||
You have successfully installed and configured Claude Code. Now you can start enjoying the
|
||||
convenience brought by AI programming assistant.
|
||||
</p>
|
||||
<p class="text-xs text-blue-200 sm:text-sm">
|
||||
If you encounter any issues during use, you can check the official documentation or community discussions for help.
|
||||
If you encounter any issues during use, you can check the official documentation or
|
||||
community discussions for help.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user