Revert "Merge pull request #292 from iRubbish/dev"

This reverts commit 9e8e74ce6b, reversing
changes made to 222f4e44fe.
This commit is contained in:
shaw
2025-08-30 20:09:41 +08:00
parent 3bc239e85c
commit a54622e3d7
16 changed files with 30 additions and 3448 deletions

View File

@@ -33,31 +33,12 @@
>名称</label
>
<input
v-model="form.name"
class="form-input w-full text-sm"
maxlength="100"
placeholder="请输入API Key名称"
required
class="form-input w-full cursor-not-allowed bg-gray-100 text-sm dark:border-gray-600 dark:bg-gray-800 dark:text-gray-400"
disabled
type="text"
:value="form.name"
/>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">最多100个字符</p>
</div>
<div>
<label
class="mb-1.5 block text-xs font-semibold text-gray-700 dark:text-gray-300 sm:mb-3 sm:text-sm"
>描述</label
>
<textarea
v-model="form.description"
class="form-input w-full text-sm"
maxlength="500"
placeholder="请输入API Key描述可选"
rows="3"
></textarea>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">
最多500个字符可选
</p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400 sm:mt-2">名称不可修改</p>
</div>
<!-- 标签 -->
@@ -651,7 +632,6 @@ const unselectedTags = computed(() => {
// 表单数据
const form = reactive({
name: '',
description: '',
tokenLimit: '',
rateLimitWindow: '',
rateLimitRequests: '',
@@ -727,8 +707,6 @@ const updateApiKey = async () => {
try {
// 准备提交的数据
const data = {
name: form.name,
description: form.description,
tokenLimit:
form.tokenLimit !== '' && form.tokenLimit !== null ? parseInt(form.tokenLimit) : 0,
rateLimitWindow:
@@ -915,7 +893,6 @@ onMounted(async () => {
}
form.name = props.apiKey.name
form.description = props.apiKey.description || ''
form.tokenLimit = props.apiKey.tokenLimit || ''
form.rateLimitWindow = props.apiKey.rateLimitWindow || ''
form.rateLimitRequests = props.apiKey.rateLimitRequests || ''

View File

@@ -1,481 +0,0 @@
<template>
<div class="space-y-6">
<!-- API Key 创建区域 -->
<div class="glass-strong rounded-3xl p-6 shadow-xl">
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3">
<i class="fas fa-key text-2xl text-blue-500" />
<div>
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-100">API Keys 管理</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">每个用户只能创建一个 API Key</p>
</div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">{{ apiKeys.length }}/1 Key</div>
</div>
<!-- 创建新 API Key -->
<div v-if="apiKeys.length === 0" class="space-y-4">
<div
class="rounded-xl border-2 border-dashed border-gray-300 bg-gray-50/50 p-6 text-center dark:border-gray-600 dark:bg-gray-800/50"
>
<i class="fas fa-plus-circle mb-3 text-3xl text-gray-400" />
<h4 class="mb-2 text-lg font-medium text-gray-700 dark:text-gray-300">
创建您的第一个 API Key
</h4>
<p class="mb-4 text-sm text-gray-500 dark:text-gray-400">
API Key 将用于访问 Claude Relay Service
</p>
<form class="mx-auto max-w-md space-y-4" @submit.prevent="createApiKey">
<div class="text-center">
<p class="mb-2 text-sm text-gray-600 dark:text-gray-400">
API Key 名称将自动设置为您的用户名
</p>
<p class="text-sm text-gray-500 dark:text-gray-500">使用额度无限制</p>
</div>
<button
class="w-full rounded-xl bg-gradient-to-r from-blue-500 to-purple-600 px-6 py-3 font-medium text-white transition-all hover:from-blue-600 hover:to-purple-700 disabled:opacity-50"
:disabled="createLoading"
type="submit"
>
<div v-if="createLoading" class="flex items-center justify-center gap-2">
<div
class="h-4 w-4 animate-spin rounded-full border-2 border-white border-t-transparent"
></div>
创建中...
</div>
<div v-else class="flex items-center justify-center gap-2">
<i class="fas fa-plus" />
创建 API Key
</div>
</button>
</form>
</div>
</div>
</div>
<!-- 现有 API Keys 显示 -->
<div v-if="apiKeys.length > 0" class="space-y-4">
<div
v-for="apiKey in apiKeys"
:key="apiKey.id"
class="glass-strong rounded-3xl p-6 shadow-xl"
>
<div class="mb-4 flex items-start justify-between">
<div class="flex items-start gap-4">
<div
class="flex h-12 w-12 items-center justify-center rounded-xl bg-gradient-to-br from-blue-500/20 to-purple-500/20"
>
<i class="fas fa-key text-lg text-blue-600 dark:text-blue-400" />
</div>
<div>
<h4 class="text-lg font-semibold text-gray-800 dark:text-gray-100">
{{ apiKey.name || '未命名 API Key' }}
</h4>
<p class="text-sm text-gray-600 dark:text-gray-400">
创建时间{{ formatDate(apiKey.createdAt) }}
</p>
<div class="mt-2 flex items-center justify-between">
<span
class="inline-flex items-center gap-1 rounded-full px-2 py-1 text-xs font-medium"
:class="
apiKey.isActive
? 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-400'
: 'bg-red-100 text-red-800 dark:bg-red-900/20 dark:text-red-400'
"
>
<i :class="apiKey.isActive ? 'fas fa-check-circle' : 'fas fa-times-circle'" />
{{ apiKey.isActive ? '活跃' : '已禁用' }}
</span>
<!-- 操作按钮 -->
<div class="flex items-center gap-2">
<button
:class="[
apiKey.isActive
? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-orange-900/20'
: 'text-green-600 hover:bg-green-50 dark:text-green-400 dark:hover:bg-green-900/20',
'rounded-lg px-3 py-1 text-xs font-medium transition-colors'
]"
@click="toggleApiKeyStatus(apiKey)"
>
<i :class="['fas mr-1', apiKey.isActive ? 'fa-ban' : 'fa-check-circle']" />
{{ apiKey.isActive ? '禁用' : '激活' }}
</button>
<button
class="rounded-lg px-3 py-1 text-xs font-medium text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/20"
@click="deleteApiKey(apiKey)"
>
<i class="fas fa-trash mr-1" />删除
</button>
</div>
</div>
</div>
</div>
</div>
<!-- API Key 显示 - 历史Key无法显示原始内容 -->
<div class="mb-4 space-y-3">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">
API Key
</label>
<div
class="rounded-xl border border-amber-300 bg-amber-50 p-4 dark:border-amber-600 dark:bg-amber-900/20"
>
<div class="flex items-center gap-2 text-amber-800 dark:text-amber-200">
<i class="fas fa-info-circle" />
<span class="text-sm">
已关联的历史API
Key无法显示原始内容仅在创建时可见如需查看完整Key请删除原key创建新Key
</span>
</div>
<div class="mt-2 text-xs text-amber-600 dark:text-amber-400">
Key ID: {{ apiKey.id }}
</div>
</div>
</div>
<!-- 使用统计 -->
<div class="grid grid-cols-1 gap-4 md:grid-cols-3">
<div class="rounded-xl bg-blue-50 p-4 dark:bg-blue-900/20">
<div class="flex items-center gap-3">
<i class="fas fa-chart-bar text-blue-500" />
<div>
<p class="text-sm font-medium text-blue-800 dark:text-blue-300">今日请求</p>
<p class="text-xl font-bold text-blue-900 dark:text-blue-100">
{{ apiKey.usage?.daily?.requests?.toLocaleString() || 0 }}
</p>
</div>
</div>
</div>
<div class="rounded-xl bg-green-50 p-4 dark:bg-green-900/20">
<div class="flex items-center gap-3">
<i class="fas fa-coins text-green-500" />
<div>
<p class="text-sm font-medium text-green-800 dark:text-green-300">今日Token</p>
<p class="text-xl font-bold text-green-900 dark:text-green-100">
{{ apiKey.usage?.daily?.tokens?.toLocaleString() || 0 }}
</p>
</div>
</div>
</div>
<div class="rounded-xl bg-purple-50 p-4 dark:bg-purple-900/20">
<div class="flex items-center gap-3">
<i class="fas fa-dollar-sign text-purple-500" />
<div>
<p class="text-sm font-medium text-purple-800 dark:text-purple-300">今日费用</p>
<p class="text-xl font-bold text-purple-900 dark:text-purple-100">
${{ (apiKey.dailyCost || 0).toFixed(4) }}
</p>
</div>
</div>
</div>
</div>
<!-- Token 额度进度条 -->
<div v-if="apiKey.tokenLimit > 0" class="mt-4">
<div class="flex items-center justify-between text-sm text-gray-600 dark:text-gray-400">
<span>Token 使用进度</span>
<span>
{{ apiKey.usage?.total?.tokens?.toLocaleString() || 0 }} /
{{ apiKey.tokenLimit?.toLocaleString() || 0 }}
</span>
</div>
<div class="mt-2 h-3 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div
class="h-full rounded-full bg-gradient-to-r from-blue-500 to-purple-600 transition-all duration-500"
:style="{
width: `${Math.min(calculateTokenUsagePercentage(apiKey.usage?.total?.tokens || 0, apiKey.tokenLimit || 0), 100)}%`
}"
></div>
</div>
</div>
<!-- 费用限制进度条 -->
<div v-if="apiKey.dailyCostLimit > 0" class="mt-4">
<div class="flex items-center justify-between text-sm text-gray-600 dark:text-gray-400">
<span>每日费用限制</span>
<span>
${{ (apiKey.dailyCost || 0).toFixed(4) }} / ${{
(apiKey.dailyCostLimit || 0).toFixed(2)
}}
</span>
</div>
<div class="mt-2 h-3 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div
class="h-full rounded-full bg-gradient-to-r from-green-500 to-red-500 transition-all duration-500"
:style="{
width: `${Math.min(calculateCostUsagePercentage(apiKey.dailyCost || 0, apiKey.dailyCostLimit || 0), 100)}%`
}"
></div>
</div>
</div>
<!-- 查看详细统计按钮 -->
<div class="mt-4">
<button
class="flex w-full items-center justify-center gap-2 rounded-lg bg-gradient-to-r from-indigo-500 to-purple-600 px-4 py-2.5 text-sm font-medium text-white transition-all duration-200 hover:from-indigo-600 hover:to-purple-700 hover:shadow-lg"
@click="showUsageDetails(apiKey)"
>
<i class="fas fa-chart-line" />
查看详细统计
</button>
</div>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading" class="flex items-center justify-center py-8">
<div class="flex items-center gap-3">
<div
class="h-6 w-6 animate-spin rounded-full border-2 border-blue-500 border-t-transparent"
></div>
<span class="text-gray-600 dark:text-gray-400">加载中...</span>
</div>
</div>
<!-- 错误提示 -->
<div
v-if="error"
class="rounded-xl border border-red-500/30 bg-red-500/20 p-4 text-center text-red-800 dark:text-red-400"
>
<i class="fas fa-exclamation-triangle mr-2" />{{ error }}
</div>
<!-- 成功提示 -->
<div
v-if="successMessage"
class="rounded-xl border border-green-500/30 bg-green-500/20 p-4 text-center text-green-800 dark:text-green-400"
>
<i class="fas fa-check-circle mr-2" />{{ successMessage }}
</div>
<!-- 使用详情模态框 -->
<UsageDetailModal
:api-key="selectedApiKeyForDetail || {}"
:show="showUsageDetailModal"
@close="showUsageDetailModal = false"
/>
<!-- 新API Key模态框 -->
<NewApiKeyModal
v-if="showNewApiKeyModal"
:api-key="newApiKeyData"
@close="showNewApiKeyModal = false"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import UsageDetailModal from '@/components/apikeys/UsageDetailModal.vue'
import NewApiKeyModal from '@/components/apikeys/NewApiKeyModal.vue'
defineProps({
userInfo: {
type: Object,
required: true
}
})
// 响应式数据
const loading = ref(false)
const createLoading = ref(false)
const error = ref('')
const successMessage = ref('')
const apiKeys = ref([])
// 使用详情模态框相关
const showUsageDetailModal = ref(false)
const selectedApiKeyForDetail = ref(null)
// 新API Key模态框相关
const showNewApiKeyModal = ref(false)
const newApiKeyData = ref(null)
const newKeyForm = ref({})
// 获取用户的 API Keys
const fetchApiKeys = async () => {
loading.value = true
error.value = ''
try {
const token = localStorage.getItem('user_token')
const response = await fetch('/admin/ldap/user/api-keys', {
headers: {
Authorization: `Bearer ${token}`
}
})
const result = await response.json()
if (result.success) {
apiKeys.value = result.apiKeys
} else {
error.value = result.message || '获取 API Keys 失败'
}
} catch (err) {
console.error('获取 API Keys 错误:', err)
error.value = '网络错误,请重试'
} finally {
loading.value = false
}
}
// 创建新的 API Key
const createApiKey = async () => {
createLoading.value = true
error.value = ''
successMessage.value = ''
try {
const token = localStorage.getItem('user_token')
const response = await fetch('/admin/ldap/user/api-keys', {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
// name和limit字段都由后端自动生成/设置
// name: 用户displayName
// limit: 0 (无限制)
})
})
const result = await response.json()
if (result.success) {
// 显示新API Key模态框
newApiKeyData.value = result.apiKey
showNewApiKeyModal.value = true
// 更新API Keys列表
apiKeys.value = [result.apiKey]
newKeyForm.value = {}
// 清除错误信息
error.value = ''
} else {
error.value = result.message || 'API Key 创建失败'
}
} catch (err) {
console.error('创建 API Key 错误:', err)
error.value = '网络错误,请重试'
} finally {
createLoading.value = false
}
}
// 计算Token使用百分比
const calculateTokenUsagePercentage = (used, limit) => {
if (!limit || limit === 0) return 0
return Math.round((used / limit) * 100)
}
// 计算费用使用百分比
const calculateCostUsagePercentage = (used, limit) => {
if (!limit || limit === 0) return 0
return Math.round((used / limit) * 100)
}
// 切换API Key状态
const toggleApiKeyStatus = async (apiKey) => {
const action = apiKey.isActive ? '禁用' : '激活'
if (confirm(`确定要${action}这个API Key吗`)) {
await updateApiKey(apiKey.id, { isActive: !apiKey.isActive })
}
}
// 删除API Key
const deleteApiKey = async (apiKey) => {
if (confirm(`确定要删除API Key "${apiKey.name}" 吗?删除后将无法恢复!`)) {
try {
const token = localStorage.getItem('user_token')
const response = await fetch(`/admin/ldap/user/api-keys/${apiKey.id}`, {
method: 'DELETE',
headers: {
Authorization: `Bearer ${token}`
}
})
const result = await response.json()
if (result.success) {
successMessage.value = 'API Key 删除成功'
// 从本地数组中移除
const index = apiKeys.value.findIndex((k) => k.id === apiKey.id)
if (index > -1) {
apiKeys.value.splice(index, 1)
}
setTimeout(() => {
successMessage.value = ''
}, 3000)
} else {
error.value = result.message || 'API Key 删除失败'
}
} catch (err) {
console.error('删除 API Key 错误:', err)
error.value = '网络错误,请重试'
}
}
}
// 更新API Key
const updateApiKey = async (keyId, updates) => {
try {
const token = localStorage.getItem('user_token')
const response = await fetch(`/admin/ldap/user/api-keys/${keyId}`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(updates)
})
const result = await response.json()
if (result.success) {
successMessage.value = 'API Key 更新成功'
// 更新本地数据
const apiKey = apiKeys.value.find((k) => k.id === keyId)
if (apiKey) {
Object.assign(apiKey, updates)
}
setTimeout(() => {
successMessage.value = ''
}, 3000)
} else {
error.value = result.message || 'API Key 更新失败'
}
} catch (err) {
console.error('更新 API Key 错误:', err)
error.value = '网络错误,请重试'
}
}
// 格式化日期
const formatDate = (dateString) => {
if (!dateString) return ''
const date = new Date(dateString)
return date.toLocaleDateString('zh-CN', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
// 显示使用详情
const showUsageDetails = (apiKey) => {
selectedApiKeyForDetail.value = apiKey
showUsageDetailModal.value = true
}
// 初始化
onMounted(() => {
fetchApiKeys()
})
</script>
<style scoped>
/* 使用全局样式 */
</style>

View File

@@ -1,268 +0,0 @@
<template>
<div class="space-y-6">
<!-- 统计概览卡片 -->
<div class="glass-strong rounded-3xl p-6 shadow-xl">
<div class="mb-6 flex items-center justify-between">
<div class="flex items-center gap-3">
<i class="fas fa-chart-line text-2xl text-blue-500" />
<div>
<h3 class="text-xl font-bold text-gray-800 dark:text-gray-100">使用统计</h3>
<p class="text-sm text-gray-600 dark:text-gray-400">您的 API 使用情况概览</p>
</div>
</div>
<button
class="rounded-xl bg-blue-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-600"
:disabled="loading"
@click="refreshStats"
>
<i class="fas fa-sync-alt mr-1" :class="{ 'animate-spin': loading }" />
刷新
</button>
</div>
<!-- 统计卡片网格 -->
<div v-if="stats" class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<!-- API Key 数量 -->
<div
class="rounded-xl bg-gradient-to-br from-blue-500/10 to-blue-600/10 p-4 dark:from-blue-500/5 dark:to-blue-600/5"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-blue-800 dark:text-blue-300">API Keys</p>
<p class="text-2xl font-bold text-blue-900 dark:text-blue-100">
{{ stats.keyCount }}
</p>
</div>
<div class="rounded-full bg-blue-500/20 p-2">
<i class="fas fa-key text-blue-600 dark:text-blue-400" />
</div>
</div>
</div>
<!-- 总使用量 -->
<div
class="rounded-xl bg-gradient-to-br from-green-500/10 to-green-600/10 p-4 dark:from-green-500/5 dark:to-green-600/5"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-green-800 dark:text-green-300">总使用量</p>
<p class="text-2xl font-bold text-green-900 dark:text-green-100">
{{ stats.totalUsage.toLocaleString() }}
</p>
</div>
<div class="rounded-full bg-green-500/20 p-2">
<i class="fas fa-chart-bar text-green-600 dark:text-green-400" />
</div>
</div>
</div>
<!-- 总额度 -->
<div
class="rounded-xl bg-gradient-to-br from-purple-500/10 to-purple-600/10 p-4 dark:from-purple-500/5 dark:to-purple-600/5"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-purple-800 dark:text-purple-300">总额度</p>
<p class="text-2xl font-bold text-purple-900 dark:text-purple-100">
{{ stats.totalLimit.toLocaleString() }}
</p>
</div>
<div class="rounded-full bg-purple-500/20 p-2">
<i class="fas fa-battery-three-quarters text-purple-600 dark:text-purple-400" />
</div>
</div>
</div>
<!-- 使用率 -->
<div
class="rounded-xl bg-gradient-to-br from-orange-500/10 to-orange-600/10 p-4 dark:from-orange-500/5 dark:to-orange-600/5"
>
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-orange-800 dark:text-orange-300">使用率</p>
<p class="text-2xl font-bold text-orange-900 dark:text-orange-100">
{{ stats.percentage }}%
</p>
</div>
<div class="rounded-full bg-orange-500/20 p-2">
<i class="fas fa-percentage text-orange-600 dark:text-orange-400" />
</div>
</div>
</div>
</div>
<!-- 加载状态 -->
<div v-if="loading && !stats" class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4">
<div
v-for="i in 4"
:key="i"
class="animate-pulse rounded-xl bg-gray-200/50 p-4 dark:bg-gray-700/50"
>
<div class="space-y-3">
<div class="h-4 w-20 rounded bg-gray-300/70 dark:bg-gray-600/70"></div>
<div class="h-8 w-16 rounded bg-gray-300/70 dark:bg-gray-600/70"></div>
</div>
</div>
</div>
</div>
<!-- 各个 API Key 详细统计 -->
<div v-if="stats && stats.keys.length > 0" class="space-y-4">
<h4 class="text-lg font-semibold text-gray-800 dark:text-gray-100">API Key 详细统计</h4>
<div
v-for="keyStats in stats.keys"
:key="keyStats.id"
class="glass-strong rounded-2xl p-5 shadow-lg"
>
<div class="mb-4 flex items-start justify-between">
<div class="flex items-start gap-3">
<div
class="flex h-10 w-10 items-center justify-center rounded-lg bg-gradient-to-br from-blue-500/20 to-purple-500/20"
>
<i class="fas fa-key text-blue-600 dark:text-blue-400" />
</div>
<div>
<h5 class="font-semibold text-gray-800 dark:text-gray-100">
{{ keyStats.name || '未命名 API Key' }}
</h5>
<p class="text-sm text-gray-600 dark:text-gray-400">ID: {{ keyStats.id }}</p>
</div>
</div>
<div class="text-right">
<p class="text-sm text-gray-600 dark:text-gray-400">使用率</p>
<p class="text-lg font-bold text-gray-800 dark:text-gray-100">
{{ keyStats.percentage }}%
</p>
</div>
</div>
<!-- 使用统计条 -->
<div class="mb-3 grid grid-cols-2 gap-4 text-sm">
<div>
<span class="text-gray-600 dark:text-gray-400">已使用</span>
<span class="font-semibold text-gray-800 dark:text-gray-100">{{
keyStats.used.toLocaleString()
}}</span>
</div>
<div>
<span class="text-gray-600 dark:text-gray-400">总额度</span>
<span class="font-semibold text-gray-800 dark:text-gray-100">{{
keyStats.limit.toLocaleString()
}}</span>
</div>
</div>
<!-- 进度条 -->
<div class="relative h-2 overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700">
<div
class="h-full rounded-full bg-gradient-to-r transition-all duration-500"
:class="getProgressColor(keyStats.percentage)"
:style="{ width: `${Math.min(keyStats.percentage, 100)}%` }"
></div>
</div>
<!-- 状态警告 -->
<div
v-if="keyStats.percentage >= 90"
class="mt-3 flex items-center gap-2 text-sm text-red-600 dark:text-red-400"
>
<i class="fas fa-exclamation-triangle" />
<span>额度即将用尽请注意使用</span>
</div>
<div
v-else-if="keyStats.percentage >= 75"
class="mt-3 flex items-center gap-2 text-sm text-orange-600 dark:text-orange-400"
>
<i class="fas fa-info-circle" />
<span>额度使用较多建议关注使用情况</span>
</div>
</div>
</div>
<!-- 无数据提示 -->
<div
v-if="!loading && stats && stats.keys.length === 0"
class="glass-strong rounded-2xl p-8 text-center shadow-lg"
>
<i class="fas fa-chart-line mb-3 text-4xl text-gray-400" />
<h4 class="mb-2 text-lg font-medium text-gray-700 dark:text-gray-300">暂无使用数据</h4>
<p class="text-sm text-gray-500 dark:text-gray-400">
创建 API Key 后开始使用即可查看详细统计
</p>
</div>
<!-- 错误提示 -->
<div
v-if="error"
class="rounded-xl border border-red-500/30 bg-red-500/20 p-4 text-center text-red-800 dark:text-red-400"
>
<i class="fas fa-exclamation-triangle mr-2" />{{ error }}
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
defineProps({
userInfo: {
type: Object,
required: true
}
})
// 响应式数据
const loading = ref(false)
const error = ref('')
const stats = ref(null)
// 获取用户统计数据
const fetchStats = async () => {
loading.value = true
error.value = ''
try {
const token = localStorage.getItem('user_token')
const response = await fetch('/admin/ldap/user/usage-stats', {
headers: {
Authorization: `Bearer ${token}`
}
})
const result = await response.json()
if (result.success) {
stats.value = result.stats
} else {
error.value = result.message || '获取统计数据失败'
}
} catch (err) {
console.error('获取统计数据错误:', err)
error.value = '网络错误,请重试'
} finally {
loading.value = false
}
}
// 刷新统计数据
const refreshStats = () => {
fetchStats()
}
// 获取进度条颜色
const getProgressColor = (percentage) => {
if (percentage >= 90) return 'from-red-500 to-red-600'
if (percentage >= 75) return 'from-orange-500 to-orange-600'
if (percentage >= 50) return 'from-yellow-500 to-yellow-600'
return 'from-green-500 to-green-600'
}
// 初始化
onMounted(() => {
fetchStats()
})
</script>
<style scoped>
/* 使用全局样式 */
</style>