From eb59f9c75da0630553ee6b356f7aff5a1c192ccb Mon Sep 17 00:00:00 2001 From: t0ng7u Date: Mon, 14 Jul 2025 23:31:01 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(ui):=20enhance=20loading=20sta?= =?UTF-8?q?tes=20and=20fix=20layout=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix uptime service card bottom spacing by removing flex layout - Replace IconRotate with IconSend for request count to better represent semantic meaning - Add skeleton loading placeholders for all dashboard statistics with 500ms minimum duration - Unify avgRPM and avgTPM calculation with consistent NaN handling - Standardize skeleton usage across HeaderBar and Detail components with active animations - Remove unnecessary empty wrapper elements in skeleton implementations - Remove gradient styling from system name in header The changes improve user experience with consistent loading states, better semantic icons, and eliminate visual layout issues in the dashboard cards. --- web/src/components/layout/HeaderBar.js | 63 ++++++++++++++++++++------ web/src/pages/Detail/index.js | 30 ++++++++++-- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/web/src/components/layout/HeaderBar.js b/web/src/components/layout/HeaderBar.js index b7425645..98a7e17b 100644 --- a/web/src/components/layout/HeaderBar.js +++ b/web/src/components/layout/HeaderBar.js @@ -221,7 +221,16 @@ const HeaderBar = () => { .fill(null) .map((_, index) => (
- + + } + />
)); } @@ -272,9 +281,22 @@ const HeaderBar = () => { if (isLoading) { return (
- + } + />
- + + } + />
); @@ -448,22 +470,35 @@ const HeaderBar = () => { /> handleNavLinkClick('home')} className="flex items-center gap-2 group ml-2"> - {isLoading ? ( - - ) : ( + + } + > logo - )} +
- {isLoading ? ( - - ) : ( - + + } + > + {systemName} - )} + {(isSelfUseMode || isDemoSiteMode) && !isLoading && ( { // ========== Hooks - Memoized Values ========== const performanceMetrics = useMemo(() => { const timeDiff = (Date.parse(end_timestamp) - Date.parse(start_timestamp)) / 60000; - const avgRPM = (times / timeDiff).toFixed(3); + const avgRPM = isNaN(times / timeDiff) ? '0' : (times / timeDiff).toFixed(3); const avgTPM = isNaN(consumeTokens / timeDiff) ? '0' : (consumeTokens / timeDiff).toFixed(3); return { avgRPM, avgTPM, timeDiff }; @@ -627,6 +628,7 @@ const Detail = (props) => { const loadQuotaData = useCallback(async () => { setLoading(true); + const startTime = Date.now(); try { let url = ''; let localStartTimestamp = Date.parse(start_timestamp) / 1000; @@ -654,7 +656,11 @@ const Detail = (props) => { showError(message); } } finally { - setLoading(false); + const elapsed = Date.now() - startTime; + const remainingTime = Math.max(0, 500 - elapsed); + setTimeout(() => { + setLoading(false); + }, remainingTime); } }, [start_timestamp, end_timestamp, username, dataExportDefaultTime, isAdminUser]); @@ -1202,10 +1208,24 @@ const Detail = (props) => {
{item.title}
-
{item.value}
+
+ + } + > + {item.value} + +
- {item.trendData && item.trendData.length > 0 && ( + {(loading || (item.trendData && item.trendData.length > 0)) && (