Compare commits

...

1 Commits

Author SHA1 Message Date
Supra4E8C
aecd5875d6 feat(usage): add loading overlay and 60s API timeout 2025-12-18 01:03:12 +08:00
3 changed files with 53 additions and 2 deletions

View File

@@ -3,9 +3,11 @@
.container { .container {
width: 100%; width: 100%;
min-height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
position: relative;
} }
.header { .header {
@@ -39,6 +41,44 @@
padding: 16px; padding: 16px;
} }
.loadingOverlay {
position: absolute;
inset: 0;
z-index: 20;
display: flex;
align-items: center;
justify-content: center;
background: rgba(243, 244, 246, 0.75);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
}
:global([data-theme='dark']) .loadingOverlay {
background: rgba(25, 25, 25, 0.72);
}
.loadingOverlayContent {
display: inline-flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-radius: $radius-lg;
border: 1px solid var(--border-color);
background: var(--bg-primary);
box-shadow: var(--shadow-lg);
:global(.loading-spinner) {
border-color: rgba(59, 130, 246, 0.25);
border-top-color: var(--primary-color);
}
}
.loadingOverlayText {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
}
// Stats Grid // Stats Grid
.statsGrid { .statsGrid {
display: grid; display: grid;

View File

@@ -16,6 +16,7 @@ import { Line } from 'react-chartjs-2';
import { Card } from '@/components/ui/Card'; import { Card } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button'; import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input'; import { Input } from '@/components/ui/Input';
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
import { IconDiamond, IconDollarSign, IconSatellite, IconTimer, IconTrendingUp } from '@/components/ui/icons'; import { IconDiamond, IconDollarSign, IconSatellite, IconTimer, IconTrendingUp } from '@/components/ui/icons';
import { useMediaQuery } from '@/hooks/useMediaQuery'; import { useMediaQuery } from '@/hooks/useMediaQuery';
import { useThemeStore } from '@/stores'; import { useThemeStore } from '@/stores';
@@ -516,6 +517,14 @@ export function UsagePage() {
return ( return (
<div className={styles.container}> <div className={styles.container}>
{loading && !usage && (
<div className={styles.loadingOverlay} aria-busy="true">
<div className={styles.loadingOverlayContent}>
<LoadingSpinner size={28} />
<span className={styles.loadingOverlayText}>{t('common.loading')}</span>
</div>
</div>
)}
<div className={styles.header}> <div className={styles.header}>
<h1 className={styles.pageTitle}>{t('usage_stats.title')}</h1> <h1 className={styles.pageTitle}>{t('usage_stats.title')}</h1>
<Button <Button

View File

@@ -5,11 +5,13 @@
import { apiClient } from './client'; import { apiClient } from './client';
import { computeKeyStats, KeyStats } from '@/utils/usage'; import { computeKeyStats, KeyStats } from '@/utils/usage';
const USAGE_TIMEOUT_MS = 60 * 1000;
export const usageApi = { export const usageApi = {
/** /**
* 获取使用统计原始数据 * 获取使用统计原始数据
*/ */
getUsage: () => apiClient.get('/usage'), getUsage: () => apiClient.get('/usage', { timeout: USAGE_TIMEOUT_MS }),
/** /**
* 计算密钥成功/失败统计,必要时会先获取 usage 数据 * 计算密钥成功/失败统计,必要时会先获取 usage 数据
@@ -17,7 +19,7 @@ export const usageApi = {
async getKeyStats(usageData?: any): Promise<KeyStats> { async getKeyStats(usageData?: any): Promise<KeyStats> {
let payload = usageData; let payload = usageData;
if (!payload) { if (!payload) {
const response = await apiClient.get('/usage'); const response = await apiClient.get('/usage', { timeout: USAGE_TIMEOUT_MS });
payload = response?.usage ?? response; payload = response?.usage ?? response;
} }
return computeKeyStats(payload); return computeKeyStats(payload);