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 {
|
.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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user