refactor: 移除仪表盘使用记录功能以避免与PR #753重叠

移除了仪表盘中的使用记录展示功能,避免与PR #753的API Key详细使用记录功能重叠:
- 移除DashboardView.vue中的使用记录表格UI及相关函数
- 移除dashboard.js中的/dashboard/usage-records接口
- 保留核心账户管理功能(账户过滤、限流状态、统计模态框等)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
IanShaw027
2025-12-03 23:37:17 -08:00
committed by IanShaw027
parent 3db268fff7
commit 0b3cf5112b
2 changed files with 1 additions and 470 deletions

View File

@@ -673,246 +673,6 @@
</div>
</div>
</div>
<!-- 最近使用记录 -->
<div class="mb-4 sm:mb-6 md:mb-8">
<div class="mb-4 flex items-center justify-between sm:mb-6">
<h3 class="text-lg font-bold text-gray-900 dark:text-gray-100 sm:text-xl">最近使用记录</h3>
<button
class="flex items-center gap-1 rounded-md border border-gray-300 bg-white px-3 py-1 text-sm font-medium text-blue-600 shadow-sm transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-800 dark:hover:bg-gray-700 sm:gap-2"
:disabled="usageRecordsLoading"
@click="loadUsageRecords"
>
<i :class="['fas fa-sync-alt text-xs', { 'animate-spin': usageRecordsLoading }]"></i>
<span class="hidden sm:inline">{{ usageRecordsLoading ? '刷新中' : '刷新' }}</span>
</button>
</div>
<div class="card p-4 sm:p-6">
<div v-if="usageRecordsLoading" class="py-12 text-center">
<div class="loading-spinner mx-auto mb-4"></div>
<p class="text-gray-500 dark:text-gray-400">正在加载使用记录...</p>
</div>
<div v-else-if="usageRecords.length === 0" class="py-12 text-center">
<p class="text-gray-500 dark:text-gray-400">暂无使用记录</p>
</div>
<div v-else class="overflow-x-auto">
<table class="w-full" style="min-width: 1100px">
<thead class="bg-gray-50 dark:bg-gray-700/50">
<tr>
<th
class="min-w-[140px] border-b border-gray-200 px-3 py-3 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
时间
</th>
<th
class="min-w-[140px] border-b border-gray-200 px-3 py-3 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
API Key
</th>
<th
class="min-w-[140px] border-b border-gray-200 px-3 py-3 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
账户
</th>
<th
class="min-w-[180px] border-b border-gray-200 px-3 py-3 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
模型
</th>
<th
class="min-w-[90px] border-b border-gray-200 px-3 py-3 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
输入
</th>
<th
class="min-w-[90px] border-b border-gray-200 px-3 py-3 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
输出
</th>
<th
class="min-w-[90px] border-b border-gray-200 px-3 py-3 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
缓存创建
</th>
<th
class="min-w-[90px] border-b border-gray-200 px-3 py-3 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
缓存读取
</th>
<th
class="min-w-[100px] border-b border-gray-200 px-3 py-3 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:border-gray-600 dark:text-gray-300"
>
成本
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200/50 dark:divide-gray-600/50">
<tr
v-for="(record, index) in usageRecords"
:key="index"
class="hover:bg-gray-50 dark:hover:bg-gray-700/30"
>
<td class="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
{{ formatRecordTime(record.timestamp) }}
</td>
<td class="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
<div class="truncate" :title="record.apiKeyName">
{{ record.apiKeyName }}
</div>
</td>
<td class="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
<div class="truncate" :title="record.accountName">
{{ record.accountName }}
</div>
</td>
<td class="px-3 py-3 text-sm text-gray-700 dark:text-gray-300">
<div class="truncate" :title="record.model">
{{ record.model }}
</div>
</td>
<td class="px-3 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
{{ formatNumber(record.inputTokens) }}
</td>
<td class="px-3 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
{{ formatNumber(record.outputTokens) }}
</td>
<td class="px-3 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
{{ formatNumber(record.cacheCreateTokens) }}
</td>
<td class="px-3 py-3 text-right text-sm text-gray-700 dark:text-gray-300">
{{ formatNumber(record.cacheReadTokens) }}
</td>
<td
class="px-3 py-3 text-right text-sm font-medium text-gray-900 dark:text-gray-100"
>
${{ formatCost(record.cost) }}
</td>
</tr>
</tbody>
</table>
<!-- 分页 -->
<div v-if="usageRecordsTotal > 0" class="mt-4 space-y-3">
<!-- 分页信息和每页数量选择 -->
<div class="flex flex-wrap items-center justify-between gap-3 text-sm">
<div class="text-gray-600 dark:text-gray-400">
显示 {{ (usageRecordsCurrentPage - 1) * usageRecordsPageSize + 1 }} -
{{ Math.min(usageRecordsCurrentPage * usageRecordsPageSize, usageRecordsTotal) }}
条,共 {{ usageRecordsTotal }} 条
</div>
<div class="flex items-center gap-2">
<span class="text-gray-600 dark:text-gray-400">每页显示:</span>
<select
v-model.number="usageRecordsPageSize"
class="rounded-lg border border-gray-300 bg-white px-2 py-1 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300"
@change="handleUsageRecordsPageSizeChange(usageRecordsPageSize)"
>
<option v-for="size in usageRecordsPageSizeOptions" :key="size" :value="size">
{{ size }} 条
</option>
</select>
</div>
</div>
<!-- 分页按钮 -->
<div class="flex justify-center gap-2">
<!-- 上一页 -->
<button
class="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
:disabled="usageRecordsCurrentPage === 1 || usageRecordsLoading"
@click="handleUsageRecordsPageChange(usageRecordsCurrentPage - 1)"
>
<i class="fas fa-chevron-left" />
</button>
<!-- 页码 -->
<template v-if="usageRecordsTotalPages <= 7">
<button
v-for="page in usageRecordsTotalPages"
:key="page"
class="min-w-[36px] rounded-lg border px-3 py-1.5 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-50"
:class="
page === usageRecordsCurrentPage
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
"
:disabled="usageRecordsLoading"
@click="handleUsageRecordsPageChange(page)"
>
{{ page }}
</button>
</template>
<template v-else>
<!-- 第一页 -->
<button
class="min-w-[36px] rounded-lg border px-3 py-1.5 text-sm transition-colors"
:class="
1 === usageRecordsCurrentPage
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
"
@click="handleUsageRecordsPageChange(1)"
>
1
</button>
<!-- 省略号 -->
<span v-if="usageRecordsCurrentPage > 3" class="px-2 text-gray-500">...</span>
<!-- 当前页附近的页码 -->
<button
v-for="page in [
usageRecordsCurrentPage - 1,
usageRecordsCurrentPage,
usageRecordsCurrentPage + 1
].filter((p) => p > 1 && p < usageRecordsTotalPages)"
:key="page"
class="min-w-[36px] rounded-lg border px-3 py-1.5 text-sm transition-colors"
:class="
page === usageRecordsCurrentPage
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
"
@click="handleUsageRecordsPageChange(page)"
>
{{ page }}
</button>
<!-- 省略号 -->
<span
v-if="usageRecordsCurrentPage < usageRecordsTotalPages - 2"
class="px-2 text-gray-500"
>...</span
>
<!-- 最后一页 -->
<button
class="min-w-[36px] rounded-lg border px-3 py-1.5 text-sm transition-colors"
:class="
usageRecordsTotalPages === usageRecordsCurrentPage
? 'border-blue-500 bg-blue-500 text-white'
: 'border-gray-300 bg-white text-gray-700 hover:bg-gray-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600'
"
@click="handleUsageRecordsPageChange(usageRecordsTotalPages)"
>
{{ usageRecordsTotalPages }}
</button>
</template>
<!-- 下一页 -->
<button
class="rounded-lg border border-gray-300 bg-white px-3 py-1.5 text-sm text-gray-700 transition-colors hover:bg-gray-50 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 dark:hover:bg-gray-600"
:disabled="
usageRecordsCurrentPage === usageRecordsTotalPages || usageRecordsLoading
"
@click="handleUsageRecordsPageChange(usageRecordsCurrentPage + 1)"
>
<i class="fas fa-chevron-right" />
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -921,22 +681,12 @@ import { ref, onMounted, onUnmounted, watch, nextTick, computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useDashboardStore } from '@/stores/dashboard'
import { useThemeStore } from '@/stores/theme'
import { apiClient } from '@/config/api'
import { showToast } from '@/utils/toast'
import Chart from 'chart.js/auto'
const dashboardStore = useDashboardStore()
const themeStore = useThemeStore()
const { isDarkMode } = storeToRefs(themeStore)
// 使用记录相关
const usageRecords = ref([])
const usageRecordsLoading = ref(false)
const usageRecordsTotal = ref(0)
const usageRecordsCurrentPage = ref(1)
const usageRecordsPageSize = ref(20)
const usageRecordsPageSizeOptions = [10, 20, 50, 100]
const {
dashboardData,
costsData,
@@ -1727,94 +1477,13 @@ watch(accountUsageTrendData, () => {
nextTick(() => createAccountUsageTrendChart())
})
// 加载使用记录
async function loadUsageRecords() {
if (usageRecordsLoading.value) return
try {
usageRecordsLoading.value = true
const offset = (usageRecordsCurrentPage.value - 1) * usageRecordsPageSize.value
const response = await apiClient.get('/admin/dashboard/usage-records', {
params: {
limit: usageRecordsPageSize.value,
offset: offset
}
})
if (response.success && response.data) {
usageRecords.value = response.data.records || []
usageRecordsTotal.value = response.data.total || 0
}
} catch (error) {
console.error('Failed to load usage records:', error)
showToast('加载使用记录失败', 'error')
} finally {
usageRecordsLoading.value = false
}
}
// 切换页码
function handleUsageRecordsPageChange(page) {
usageRecordsCurrentPage.value = page
loadUsageRecords()
}
// 切换每页数量
function handleUsageRecordsPageSizeChange(size) {
usageRecordsPageSize.value = size
usageRecordsCurrentPage.value = 1 // 重置到第一页
loadUsageRecords()
}
// 计算总页数
const usageRecordsTotalPages = computed(() => {
return Math.ceil(usageRecordsTotal.value / usageRecordsPageSize.value) || 1
})
// 格式化记录时间
function formatRecordTime(timestamp) {
if (!timestamp) return '-'
const date = new Date(timestamp)
const now = new Date()
const diff = now - date
// 如果是今天
if (diff < 86400000 && date.getDate() === now.getDate()) {
return date.toLocaleTimeString('zh-CN', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
})
}
// 如果是昨天
if (diff < 172800000 && date.getDate() === now.getDate() - 1) {
return '昨天 ' + date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
}
// 其他日期
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
})
}
// 格式化成本
function formatCost(cost) {
if (!cost || cost === 0) return '0.000000'
return cost.toFixed(6)
}
// 刷新所有数据
async function refreshAllData() {
if (isRefreshing.value) return
isRefreshing.value = true
try {
await Promise.all([loadDashboardData(), refreshChartsData(), loadUsageRecords()])
await Promise.all([loadDashboardData(), refreshChartsData()])
} finally {
isRefreshing.value = false
}