mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-09 17:44:46 +08:00
feat: implement last 24 hours date range preset and update filters in UsageView
This commit is contained in:
@@ -5,10 +5,20 @@
|
||||
<!-- Charts Section -->
|
||||
<div class="space-y-4">
|
||||
<div class="card p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.granularity') }}:</span>
|
||||
<div class="w-28">
|
||||
<Select v-model="granularity" :options="granularityOptions" @change="loadChartData" />
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.timeRange') }}:</span>
|
||||
<DateRangePicker
|
||||
v-model:start-date="startDate"
|
||||
v-model:end-date="endDate"
|
||||
@change="onDateRangeChange"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-auto flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ t('admin.dashboard.granularity') }}:</span>
|
||||
<div class="w-28">
|
||||
<Select v-model="granularity" :options="granularityOptions" @change="loadChartData" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -41,7 +51,7 @@
|
||||
<TokenUsageTrend :trend-data="trendData" :loading="chartsLoading" />
|
||||
</div>
|
||||
</div>
|
||||
<UsageFilters v-model="filters" v-model:startDate="startDate" v-model:endDate="endDate" :exporting="exporting" @change="applyFilters" @refresh="refreshData" @reset="resetFilters" @cleanup="openCleanupDialog" @export="exportToExcel">
|
||||
<UsageFilters v-model="filters" :start-date="startDate" :end-date="endDate" :exporting="exporting" @change="applyFilters" @refresh="refreshData" @reset="resetFilters" @cleanup="openCleanupDialog" @export="exportToExcel">
|
||||
<template #after-reset>
|
||||
<div class="relative" ref="columnDropdownRef">
|
||||
<button
|
||||
@@ -106,7 +116,7 @@ import { useRoute } from 'vue-router'
|
||||
import { useAppStore } from '@/stores/app'; import { adminAPI } from '@/api/admin'; import { adminUsageAPI } from '@/api/admin/usage'
|
||||
import { formatReasoningEffort } from '@/utils/format'
|
||||
import { resolveUsageRequestType, requestTypeToLegacyStream } from '@/utils/usageRequestType'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'; import Pagination from '@/components/common/Pagination.vue'; import Select from '@/components/common/Select.vue'
|
||||
import AppLayout from '@/components/layout/AppLayout.vue'; import Pagination from '@/components/common/Pagination.vue'; import Select from '@/components/common/Select.vue'; import DateRangePicker from '@/components/common/DateRangePicker.vue'
|
||||
import UsageStatsCards from '@/components/admin/usage/UsageStatsCards.vue'; import UsageFilters from '@/components/admin/usage/UsageFilters.vue'
|
||||
import UsageTable from '@/components/admin/usage/UsageTable.vue'; import UsageExportProgress from '@/components/admin/usage/UsageExportProgress.vue'
|
||||
import UsageCleanupDialog from '@/components/admin/usage/UsageCleanupDialog.vue'
|
||||
@@ -158,9 +168,22 @@ const formatLD = (d: Date) => {
|
||||
const day = String(d.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
const getTodayLocalDate = () => formatLD(new Date())
|
||||
const getGranularityForRange = (start: string, end: string): 'day' | 'hour' => start === end ? 'hour' : 'day'
|
||||
const startDate = ref(getTodayLocalDate()); const endDate = ref(getTodayLocalDate())
|
||||
const getLast24HoursRangeDates = (): { start: string; end: string } => {
|
||||
const end = new Date()
|
||||
const start = new Date(end.getTime() - 24 * 60 * 60 * 1000)
|
||||
return {
|
||||
start: formatLD(start),
|
||||
end: formatLD(end)
|
||||
}
|
||||
}
|
||||
const getGranularityForRange = (start: string, end: string): 'day' | 'hour' => {
|
||||
const startTime = new Date(`${start}T00:00:00`).getTime()
|
||||
const endTime = new Date(`${end}T00:00:00`).getTime()
|
||||
const daysDiff = Math.ceil((endTime - startTime) / (1000 * 60 * 60 * 24))
|
||||
return daysDiff <= 1 ? 'hour' : 'day'
|
||||
}
|
||||
const defaultRange = getLast24HoursRangeDates()
|
||||
const startDate = ref(defaultRange.start); const endDate = ref(defaultRange.end)
|
||||
const filters = ref<AdminUsageQueryParams>({ user_id: undefined, model: undefined, group_id: undefined, request_type: undefined, billing_type: null, start_date: startDate.value, end_date: endDate.value })
|
||||
const pagination = reactive({ page: 1, page_size: 20, total: 0 })
|
||||
|
||||
@@ -197,6 +220,18 @@ const applyRouteQueryFilters = () => {
|
||||
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
||||
}
|
||||
|
||||
const onDateRangeChange = (range: { startDate: string; endDate: string; preset: string | null }) => {
|
||||
startDate.value = range.startDate
|
||||
endDate.value = range.endDate
|
||||
filters.value = {
|
||||
...filters.value,
|
||||
start_date: range.startDate,
|
||||
end_date: range.endDate
|
||||
}
|
||||
granularity.value = getGranularityForRange(range.startDate, range.endDate)
|
||||
applyFilters()
|
||||
}
|
||||
|
||||
const loadLogs = async () => {
|
||||
abortController?.abort(); const c = new AbortController(); abortController = c; loading.value = true
|
||||
try {
|
||||
@@ -260,7 +295,14 @@ const loadChartData = async () => {
|
||||
}
|
||||
const applyFilters = () => { pagination.page = 1; loadLogs(); loadStats(); loadChartData() }
|
||||
const refreshData = () => { loadLogs(); loadStats(); loadChartData() }
|
||||
const resetFilters = () => { startDate.value = getTodayLocalDate(); endDate.value = getTodayLocalDate(); filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null }; granularity.value = getGranularityForRange(startDate.value, endDate.value); applyFilters() }
|
||||
const resetFilters = () => {
|
||||
const range = getLast24HoursRangeDates()
|
||||
startDate.value = range.start
|
||||
endDate.value = range.end
|
||||
filters.value = { start_date: startDate.value, end_date: endDate.value, request_type: undefined, billing_type: null }
|
||||
granularity.value = getGranularityForRange(startDate.value, endDate.value)
|
||||
applyFilters()
|
||||
}
|
||||
const handlePageChange = (p: number) => { pagination.page = p; loadLogs() }
|
||||
const handlePageSizeChange = (s: number) => { pagination.page_size = s; pagination.page = 1; loadLogs() }
|
||||
const cancelExport = () => exportAbortController?.abort()
|
||||
|
||||
@@ -19,11 +19,19 @@ const { list, getStats, getSnapshotV2, getById } = vi.hoisted(() => {
|
||||
})
|
||||
|
||||
const messages: Record<string, string> = {
|
||||
'admin.dashboard.timeRange': 'Time Range',
|
||||
'admin.dashboard.day': 'Day',
|
||||
'admin.dashboard.hour': 'Hour',
|
||||
'admin.usage.failedToLoadUser': 'Failed to load user',
|
||||
}
|
||||
|
||||
const formatLocalDate = (date: Date): string => {
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
return `${year}-${month}-${day}`
|
||||
}
|
||||
|
||||
vi.mock('@/api/admin', () => ({
|
||||
adminAPI: {
|
||||
usage: {
|
||||
@@ -68,6 +76,12 @@ vi.mock('vue-i18n', async () => {
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('vue-router', () => ({
|
||||
useRoute: () => ({
|
||||
query: {}
|
||||
})
|
||||
}))
|
||||
|
||||
const AppLayoutStub = { template: '<div><slot /></div>' }
|
||||
const UsageFiltersStub = { template: '<div><slot name="after-reset" /></div>' }
|
||||
const ModelDistributionChartStub = {
|
||||
@@ -138,6 +152,7 @@ describe('admin UsageView distribution metric toggles', () => {
|
||||
UserBalanceHistoryModal: true,
|
||||
Pagination: true,
|
||||
Select: true,
|
||||
DateRangePicker: true,
|
||||
Icon: true,
|
||||
TokenUsageTrend: true,
|
||||
ModelDistributionChart: ModelDistributionChartStub,
|
||||
@@ -150,6 +165,13 @@ describe('admin UsageView distribution metric toggles', () => {
|
||||
await flushPromises()
|
||||
|
||||
expect(getSnapshotV2).toHaveBeenCalledTimes(1)
|
||||
const now = new Date()
|
||||
const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
expect(getSnapshotV2).toHaveBeenCalledWith(expect.objectContaining({
|
||||
start_date: formatLocalDate(yesterday),
|
||||
end_date: formatLocalDate(now),
|
||||
granularity: 'hour'
|
||||
}))
|
||||
|
||||
const modelChart = wrapper.find('[data-test="model-chart"]')
|
||||
const groupChart = wrapper.find('[data-test="group-chart"]')
|
||||
|
||||
Reference in New Issue
Block a user