diff --git a/web/src/components/dashboard/BentoTrend.jsx b/web/src/components/dashboard/BentoTrend.jsx
index 912bfd7..ae8f3ad 100644
--- a/web/src/components/dashboard/BentoTrend.jsx
+++ b/web/src/components/dashboard/BentoTrend.jsx
@@ -17,7 +17,7 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
-import React, { useMemo, useState } from 'react';
+import React, { useMemo, useState, useRef, useEffect } from 'react';
import { TrendingUp, RefreshCw, ChevronDown } from 'lucide-react';
import Sparkline from './Sparkline';
import { renderQuota } from '../../helpers';
@@ -28,13 +28,54 @@ const formatMD = (d) => {
return `${m}/${day}`;
};
-const GRANULARITY = [
- { value: 'hour', label: '按小时' },
- { value: 'day', label: '按天' },
- { value: 'week', label: '按周' },
- { value: 'month', label: '按月' },
+const toDateStr = (d) => {
+ const y = d.getFullYear();
+ const m = String(d.getMonth() + 1).padStart(2, '0');
+ const day = String(d.getDate()).padStart(2, '0');
+ return `${y}-${m}-${day}`;
+};
+
+const RANGE_OPTIONS = [
+ { key: 'today', label: '今天' },
+ { key: 'yesterday', label: '昨天' },
+ { key: '24h', label: '近 24 小时' },
+ { key: '7d', label: '近 7 天' },
+ { key: '14d', label: '近 14 天' },
+ { key: '30d', label: '近 30 天' },
+ { key: 'thisMonth', label: '本月' },
+ { key: 'lastMonth', label: '上月' },
];
+const getRangeDates = (key) => {
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+ switch (key) {
+ case 'today':
+ return { start: today, end: now };
+ case 'yesterday': {
+ const y = new Date(today.getTime() - 86400000);
+ return { start: y, end: new Date(today.getTime() - 1000) };
+ }
+ case '24h':
+ return { start: new Date(now.getTime() - 86400000), end: now };
+ case '7d':
+ return { start: new Date(today.getTime() - 6 * 86400000), end: now };
+ case '14d':
+ return { start: new Date(today.getTime() - 13 * 86400000), end: now };
+ case '30d':
+ return { start: new Date(today.getTime() - 29 * 86400000), end: now };
+ case 'thisMonth':
+ return { start: new Date(now.getFullYear(), now.getMonth(), 1), end: now };
+ case 'lastMonth': {
+ const lmStart = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+ const lmEnd = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
+ return { start: lmStart, end: lmEnd };
+ }
+ default:
+ return { start: new Date(today.getTime() - 6 * 86400000), end: now };
+ }
+};
+
const BentoTrend = ({
dailyStats = [],
topModels = [],
@@ -42,7 +83,31 @@ const BentoTrend = ({
onRefresh,
loading = false,
}) => {
- const [granularity, setGranularity] = useState('day');
+ const [rangeOpen, setRangeOpen] = useState(false);
+ const [selectedKey, setSelectedKey] = useState('7d');
+ const [customStart, setCustomStart] = useState('');
+ const [customEnd, setCustomEnd] = useState('');
+ const rangeRef = useRef(null);
+
+ useEffect(() => {
+ const handler = (e) => {
+ if (rangeRef.current && !rangeRef.current.contains(e.target)) {
+ setRangeOpen(false);
+ }
+ };
+ document.addEventListener('mousedown', handler);
+ return () => document.removeEventListener('mousedown', handler);
+ }, []);
+
+ const selectedLabel = RANGE_OPTIONS.find((r) => r.key === selectedKey)?.label || '近 7 天';
+
+ const handleApply = () => {
+ if (customStart && customEnd) {
+ setSelectedKey('custom');
+ }
+ setRangeOpen(false);
+ };
+
const counts = useMemo(() => dailyStats.map((d) => d.count), [dailyStats]);
const costs = useMemo(() => dailyStats.map((d) => d.cost), [dailyStats]);
@@ -64,7 +129,7 @@ const BentoTrend = ({
Token 使用趋势
- 近 7 天
+ {selectedLabel}
{totalCalls} 次
· 日均{' '}
{avg}
@@ -82,10 +147,71 @@ const BentoTrend = ({
-
-
- 近 7 天
-
+
+
+
+
+ {rangeOpen && (
+
+
+ {RANGE_OPTIONS.map((opt) => (
+
+ ))}
+
+
+
+
+
+
+ )}
+
+
-
-
粒度
-
-
-
-
-