mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 11:20:50 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aecd5875d6 |
@@ -3,9 +3,11 @@
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header {
|
||||
@@ -39,6 +41,44 @@
|
||||
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
|
||||
.statsGrid {
|
||||
display: grid;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Line } from 'react-chartjs-2';
|
||||
import { Card } from '@/components/ui/Card';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { IconDiamond, IconDollarSign, IconSatellite, IconTimer, IconTrendingUp } from '@/components/ui/icons';
|
||||
import { useMediaQuery } from '@/hooks/useMediaQuery';
|
||||
import { useThemeStore } from '@/stores';
|
||||
@@ -516,6 +517,14 @@ export function UsagePage() {
|
||||
|
||||
return (
|
||||
<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}>
|
||||
<h1 className={styles.pageTitle}>{t('usage_stats.title')}</h1>
|
||||
<Button
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
import { apiClient } from './client';
|
||||
import { computeKeyStats, KeyStats } from '@/utils/usage';
|
||||
|
||||
const USAGE_TIMEOUT_MS = 60 * 1000;
|
||||
|
||||
export const usageApi = {
|
||||
/**
|
||||
* 获取使用统计原始数据
|
||||
*/
|
||||
getUsage: () => apiClient.get('/usage'),
|
||||
getUsage: () => apiClient.get('/usage', { timeout: USAGE_TIMEOUT_MS }),
|
||||
|
||||
/**
|
||||
* 计算密钥成功/失败统计,必要时会先获取 usage 数据
|
||||
@@ -17,7 +19,7 @@ export const usageApi = {
|
||||
async getKeyStats(usageData?: any): Promise<KeyStats> {
|
||||
let payload = usageData;
|
||||
if (!payload) {
|
||||
const response = await apiClient.get('/usage');
|
||||
const response = await apiClient.get('/usage', { timeout: USAGE_TIMEOUT_MS });
|
||||
payload = response?.usage ?? response;
|
||||
}
|
||||
return computeKeyStats(payload);
|
||||
|
||||
Reference in New Issue
Block a user