From 1ff561c32cef3c68c16b47329ac5d885a8429273 Mon Sep 17 00:00:00 2001 From: wxj Date: Tue, 30 Jun 2026 11:14:25 +0800 Subject: [PATCH] =?UTF-8?q?=E6=97=B6=E9=97=B4=E8=8C=83=E5=9B=B4=E6=94=B9?= =?UTF-8?q?=E4=B8=BA=E4=B8=8B=E6=8B=89=E8=8F=9C=E5=8D=95=EF=BC=9A=E9=A2=84?= =?UTF-8?q?=E8=AE=BE=E9=80=89=E9=A1=B9=20+=20=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E6=97=A5=E6=9C=9F=E8=8C=83=E5=9B=B4=20+=20=E5=BA=94=E7=94=A8?= =?UTF-8?q?=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/src/components/dashboard/BentoTrend.jsx | 167 ++++++++++++++++---- 1 file changed, 138 insertions(+), 29 deletions(-) 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) => ( + + ))} +
+ +
+ +
+
+ setCustomStart(e.target.value)} + className='flex-1 rounded-lg border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 outline-none focus:border-slate-400 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:focus:border-white/30' + placeholder='开始日期' + /> + + setCustomEnd(e.target.value)} + className='flex-1 rounded-lg border border-slate-200 bg-white px-2 py-1 text-xs text-slate-700 outline-none focus:border-slate-400 dark:border-white/10 dark:bg-white/[0.02] dark:text-white/70 dark:focus:border-white/30' + placeholder='结束日期' + /> +
+ +
+
+ )} +
+
-
- 粒度 -
- - -
-