feat: 重新支持apikey费用排序功能

This commit is contained in:
shaw
2025-11-28 15:32:50 +08:00
parent d9476230c6
commit 28caa93d99
5 changed files with 1165 additions and 20 deletions

View File

@@ -311,8 +311,24 @@
</th>
<th
class="w-[4%] min-w-[40px] px-3 py-4 text-right text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
:class="{
'cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-600': canSortByCost,
'cursor-not-allowed opacity-60': !canSortByCost
}"
:title="costSortTooltip"
@click="sortApiKeys('cost')"
>
费用
<i
v-if="apiKeysSortBy === 'cost'"
:class="[
'fas',
apiKeysSortOrder === 'asc' ? 'fa-sort-up' : 'fa-sort-down',
'ml-1'
]"
/>
<i v-else-if="canSortByCost" class="fas fa-sort ml-1 text-gray-400" />
<i v-else class="fas fa-clock ml-1 text-gray-400" title="索引更新中" />
</th>
<th
class="w-[14%] min-w-[120px] px-3 py-4 text-left text-xs font-bold uppercase tracking-wider text-gray-700 dark:text-gray-300"
@@ -2042,7 +2058,7 @@
</template>
<script setup>
import { ref, reactive, computed, onMounted, watch } from 'vue'
import { ref, reactive, computed, onMounted, onUnmounted, watch } from 'vue'
import { showToast } from '@/utils/toast'
import { apiClient } from '@/config/api'
import { useClientsStore } from '@/stores/clients'
@@ -2113,10 +2129,13 @@ const timeRangeDropdownOptions = computed(() => [
const activeTab = ref('active')
const deletedApiKeys = ref([])
const deletedApiKeysLoading = ref(false)
const apiKeysSortBy = ref('createdAt') // 修改默认排序为创建时间(移除费用排序支持)
const apiKeysSortBy = ref('createdAt') // 默认排序为创建时间
const apiKeysSortOrder = ref('desc')
const expandedApiKeys = ref({})
// 费用排序相关状态
const costSortStatus = ref({}) // 各时间范围的索引状态
// 后端分页相关状态
const serverPagination = ref({
page: 1,
@@ -2439,14 +2458,38 @@ const loadApiKeys = async (clearStatsCache = true) => {
params.set('tag', selectedTagFilter.value)
}
// 排序参数(支持费用字段
const validSortFields = ['name', 'createdAt', 'expiresAt', 'lastUsedAt', 'isActive', 'status']
// 排序参数(支持费用排序
const validSortFields = [
'name',
'createdAt',
'expiresAt',
'lastUsedAt',
'isActive',
'status',
'cost'
]
const effectiveSortBy = validSortFields.includes(apiKeysSortBy.value)
? apiKeysSortBy.value
: 'createdAt'
params.set('sortBy', effectiveSortBy)
params.set('sortOrder', apiKeysSortOrder.value)
// 如果是费用排序,添加费用相关参数
if (effectiveSortBy === 'cost') {
if (
globalDateFilter.type === 'custom' &&
globalDateFilter.customStart &&
globalDateFilter.customEnd
) {
params.set('costTimeRange', 'custom')
params.set('costStartDate', globalDateFilter.customStart)
params.set('costEndDate', globalDateFilter.customEnd)
} else {
// 使用当前的时间范围预设
params.set('costTimeRange', globalDateFilter.preset || '7days')
}
}
// 时间范围(用于标记,不用于费用计算)
if (
globalDateFilter.type === 'custom' &&
@@ -2641,14 +2684,102 @@ const loadDeletedApiKeys = async () => {
// 排序API Keys
const sortApiKeys = (field) => {
// 费用排序特殊处理
if (field === 'cost') {
if (!canSortByCost.value) {
showToast('费用排序索引正在更新中,请稍后重试', 'warning')
return
}
// 如果是 custom 时间范围,提示可能需要等待
if (globalDateFilter.type === 'custom') {
showToast('正在计算费用排序,可能需要几秒钟...', 'info')
}
}
if (apiKeysSortBy.value === field) {
apiKeysSortOrder.value = apiKeysSortOrder.value === 'asc' ? 'desc' : 'asc'
} else {
apiKeysSortBy.value = field
apiKeysSortOrder.value = 'asc'
// 费用排序默认降序(高费用在前)
apiKeysSortOrder.value = field === 'cost' ? 'desc' : 'asc'
}
}
// 计算是否可以进行费用排序
const canSortByCost = computed(() => {
// custom 时间范围始终允许(实时计算)
if (globalDateFilter.type === 'custom') {
return true
}
// 检查对应时间范围的索引状态
const timeRange = globalDateFilter.preset
const status = costSortStatus.value[timeRange]
return status?.status === 'ready'
})
// 费用排序提示文字
const costSortTooltip = computed(() => {
if (globalDateFilter.type === 'custom') {
return '点击按费用排序(实时计算,可能需要几秒钟)'
}
const timeRange = globalDateFilter.preset
const status = costSortStatus.value[timeRange]
if (!status) {
return '费用排序索引未初始化'
}
if (status.status === 'updating') {
return '费用排序索引正在更新中...'
}
if (status.status === 'ready') {
const lastUpdate = status.lastUpdate ? new Date(status.lastUpdate).toLocaleString() : '未知'
return `点击按费用排序(索引更新于: ${lastUpdate}`
}
return '费用排序索引状态未知'
})
// 费用排序索引状态刷新定时器
let costSortStatusTimer = null
// 获取费用排序索引状态
const fetchCostSortStatus = async () => {
try {
const data = await apiClient.get('/admin/api-keys/cost-sort-status')
if (data.success) {
costSortStatus.value = data.data || {}
// 根据索引状态动态调整刷新间隔
scheduleNextCostSortStatusRefresh()
}
} catch (error) {
console.error('Failed to fetch cost sort status:', error)
}
}
// 智能调度下次状态刷新
const scheduleNextCostSortStatusRefresh = () => {
// 清除旧的定时器
if (costSortStatusTimer) {
clearTimeout(costSortStatusTimer)
}
// 检查是否有任何索引正在更新中
const hasUpdating = Object.values(costSortStatus.value).some(
(status) => status?.status === 'updating'
)
// 如果有索引正在更新使用短间隔10秒否则使用长间隔60秒
const interval = hasUpdating ? 10000 : 60000
costSortStatusTimer = setTimeout(fetchCostSortStatus, interval)
}
// 格式化数字
const formatNumber = (num) => {
if (!num && num !== 0) return '0'
@@ -3268,6 +3399,23 @@ const calculatePeriodCost = (key) => {
// 处理时间范围下拉框变化
const handleTimeRangeChange = (value) => {
setGlobalDateFilterPreset(value)
// 如果当前是费用排序,检查新时间范围的索引是否就绪
if (apiKeysSortBy.value === 'cost') {
// custom 时间范围始终允许(实时计算)
if (value === 'custom') {
return
}
// 检查新时间范围的索引状态
const status = costSortStatus.value[value]
if (!status || status.status !== 'ready') {
// 索引未就绪,回退到默认排序
apiKeysSortBy.value = 'createdAt'
apiKeysSortOrder.value = 'desc'
showToast('当前时间范围的费用排序索引未就绪,已切换到默认排序', 'info')
}
}
}
// 设置全局日期预设
@@ -4502,6 +4650,9 @@ watch(apiKeys, () => {
})
onMounted(async () => {
// 获取费用排序索引状态(不阻塞,会自动调度后续刷新)
fetchCostSortStatus()
// 先加载 API Keys优先显示列表
await Promise.all([clientsStore.loadSupportedClients(), loadApiKeys()])
@@ -4511,6 +4662,14 @@ onMounted(async () => {
// 异步加载账号数据(不阻塞页面显示)
loadAccounts()
})
// 组件卸载时清理定时器
onUnmounted(() => {
if (costSortStatusTimer) {
clearTimeout(costSortStatusTimer)
costSortStatusTimer = null
}
})
</script>
<style scoped>