diff --git a/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts b/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts index a73308b..3e18d3c 100644 --- a/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts +++ b/frontend/src/pages/analysis/hooks/useAnalysisMetrics.ts @@ -75,6 +75,11 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna const [appLabel, setAppLabel] = useState('app') const [data, setData] = useState(null) const [loading, setLoading] = useState(false) + // appsLoading 单独跟踪"按应用统计"这段重查询(10~30 秒)。 + // 它与 loading 分开的原因:上面索引类指标 100ms 就回来了, + // 如果共用一个 loading,页面会在"索引已好、app 还没好"的空档里 + // 显示成"没有应用维度的数据"——用户等了很久以为没数据,其实还在算。 + const [appsLoading, setAppsLoading] = useState(false) const [error, setError] = useState('') const [refreshTick, setRefreshTick] = useState(0) const abortRef = useRef(null) @@ -192,6 +197,7 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna setLoading(false) return } + setAppsLoading(true) // 这两条并行而不是串行:实测总量 22s、error 8.6s,串行 31s,并行 22s。 // 并发上限是 4,两条并行不会撞护栏(之前串行是为了避免 5 条并发的场景)。 @@ -269,12 +275,15 @@ export function useAnalysisMetrics({ sourceId, start, end, namespace, rankingEna if ((err as Error).name === 'AbortError') return setError(err instanceof Error ? err.message : 'metrics failed') } finally { - if (version === versionRef.current) setLoading(false) + if (version === versionRef.current) { + setLoading(false) + setAppsLoading(false) + } } })() return () => { controller.abort() } }, [sourceId, start, end, namespace, appLabel, refreshTick, rankingEnabled]) - return { data, loading, error, appLabel, refresh } + return { data, loading, appsLoading, error, appLabel, refresh } } diff --git a/frontend/src/pages/analysis/index.tsx b/frontend/src/pages/analysis/index.tsx index e31fea6..0090bba 100644 --- a/frontend/src/pages/analysis/index.tsx +++ b/frontend/src/pages/analysis/index.tsx @@ -144,7 +144,7 @@ const AnalysisPage = memo(function AnalysisPage() { // 都依赖 by-app 数据,而它原来被底部排行卡片的可见性门控 —— // 用户不滚到底就永远拿不到数据,表现为"三条都没有应用维度的数据"。 // 现在进页面就发,页面用两阶段渲染:索引类指标先出,app 维度到货后补。 - const { data, loading, error, appLabel, refresh } = useAnalysisMetrics({ + const { data, loading, appsLoading, error, appLabel, refresh } = useAnalysisMetrics({ sourceId: effectiveSourceId, start, end, @@ -170,7 +170,9 @@ const AnalysisPage = memo(function AnalysisPage() { const daily = useDailyVolume({ sourceId: effectiveSourceId, days: dailyDays, - enabled: dailyView.inView, + // 不再按可见性懒加载:这个接口只读平台库,实测 9ms。 + // 之前门控 + ref 观察器导致"滚到底也不加载",看起来就是"每日日志量一直没数据"。 + enabled: Boolean(effectiveSourceId), }) // 非 admin:菜单不可见,直访时给明确提示(后端 AdminAuth 403 兜底) @@ -248,7 +250,14 @@ const AnalysisPage = memo(function AnalysisPage() { ) : loading && !data ? ( - ) : !data || !sourceId ? null : ( + ) : !data || !sourceId ? ( + /* data 为 null 时原来直接 return null → 整页空白,用户以为"没数据"。 + 这里明确显示"正在统计"而不是什么都不画。 */ +
+ +

{t('table.computing')}

+
+ ) : ( <> {/* 不再显示"告警行数"卡片:为了把它从 3 条重查询降到 2 条, 已放弃全局 warn 统计。显示一个恒为 0 的数字比不显示更误导。 */} @@ -298,7 +307,7 @@ const AnalysisPage = memo(function AnalysisPage() { {/* 副标题说明这块不需要读日志内容,避免和上面的排行混淆 */} - {loading && !data.apps.length ? ( + {(appsLoading || loading) && !data.apps.length ? ( // 这块必须按 app 分组统计(读日志内容),实测大集群 15 分钟约 20~30 秒。 // 明确告诉用户在算什么、要等多久,比空表格或整页转圈好。