From b89837d22ec9909f09db3eddbd15329bad3e5129 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 12 Dec 2025 15:20:37 +0800 Subject: [PATCH] =?UTF-8?q?feat(DeepAnalysis):=20=E5=AE=9E=E7=8E=B0=20Tab?= =?UTF-8?q?=20=E6=87=92=E5=8A=A0=E8=BD=BD=EF=BC=8C=E6=8C=89=E9=9C=80?= =?UTF-8?q?=E8=AF=B7=E6=B1=82=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DeepAnalysis/index.js: 重构为懒加载模式 - 添加 TAB_API_MAP 映射 Tab 与接口关系 - 战略分析/业务结构共享 comprehensive-analysis 接口 - 产业链/发展历程按需加载对应接口 - 使用 loadedApisRef 缓存已加载状态,避免重复请求 - 各接口独立 loading 状态管理 - 添加 stockCode 竞态条件保护 - DeepAnalysisTab/index.tsx: 支持受控模式 - 新增 activeTab/onTabChange props - loading 状态下保持 Tab 导航可切换 - types.ts: 新增 DeepAnalysisTabKey 类型和相关 props 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../CompanyOverview/DeepAnalysisTab/index.tsx | 51 ++++- .../CompanyOverview/DeepAnalysisTab/types.ts | 7 + .../Company/components/DeepAnalysis/index.js | 188 ++++++++++++++---- 3 files changed, 200 insertions(+), 46 deletions(-) diff --git a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx index 865c4006..35b4fb5c 100644 --- a/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx +++ b/src/views/Company/components/CompanyOverview/DeepAnalysisTab/index.tsx @@ -6,14 +6,16 @@ * 2. 业务结构 - 业务结构树 + 业务板块详情 * 3. 产业链 - 产业链分析(独立,含 Sankey 图) * 4. 发展历程 - 关键因素 + 时间线 + * + * 支持懒加载:通过 activeTab 和 onTabChange 实现按需加载数据 */ -import React from 'react'; +import React, { useMemo } from 'react'; import { Card, CardBody, Center, VStack, Spinner, Text } from '@chakra-ui/react'; import { FaBrain, FaBuilding, FaLink, FaHistory } from 'react-icons/fa'; import SubTabContainer, { type SubTabConfig } from '@components/SubTabContainer'; import { StrategyTab, BusinessTab, ValueChainTab, DevelopmentTab } from './tabs'; -import type { DeepAnalysisTabProps } from './types'; +import type { DeepAnalysisTabProps, DeepAnalysisTabKey } from './types'; // 主题配置(与 BasicInfoTab 保持一致) const THEME = { @@ -31,6 +33,16 @@ const DEEP_ANALYSIS_TABS: SubTabConfig[] = [ { key: 'development', name: '发展历程', icon: FaHistory, component: DevelopmentTab }, ]; +/** + * Tab key 到 index 的映射 + */ +const TAB_KEY_TO_INDEX: Record = { + strategy: 0, + business: 1, + valueChain: 2, + development: 3, +}; + const DeepAnalysisTab: React.FC = ({ comprehensiveData, valueChainData, @@ -39,16 +51,37 @@ const DeepAnalysisTab: React.FC = ({ cardBg, expandedSegments, onToggleSegment, + activeTab, + onTabChange, }) => { + // 计算当前 Tab 索引(受控模式) + const currentIndex = useMemo(() => { + if (activeTab) { + return TAB_KEY_TO_INDEX[activeTab] ?? 0; + } + return undefined; // 非受控模式 + }, [activeTab]); + // 加载状态 if (loading) { return ( -
- - - 加载深度分析数据... - -
+ + + +
+ + + 加载数据中... + +
+
+
); } @@ -57,6 +90,8 @@ const DeepAnalysisTab: React.FC = ({ ; onToggleSegment: (index: number) => void; + /** 当前激活的 Tab(受控模式) */ + activeTab?: DeepAnalysisTabKey; + /** Tab 切换回调(懒加载触发) */ + onTabChange?: (index: number, tabKey: string) => void; } // ==================== 子组件 Props 类型 ==================== diff --git a/src/views/Company/components/DeepAnalysis/index.js b/src/views/Company/components/DeepAnalysis/index.js index 77a3dca4..23797b97 100644 --- a/src/views/Company/components/DeepAnalysis/index.js +++ b/src/views/Company/components/DeepAnalysis/index.js @@ -1,7 +1,7 @@ // src/views/Company/components/DeepAnalysis/index.js -// 深度分析 - 独立一级 Tab 组件 +// 深度分析 - 独立一级 Tab 组件(懒加载版本) -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback, useRef } from "react"; import { logger } from "@utils/logger"; import { getApiBase } from "@utils/apiConfig"; @@ -10,27 +10,55 @@ import DeepAnalysisTab from "../CompanyOverview/DeepAnalysisTab"; const API_BASE_URL = getApiBase(); +/** + * Tab 与 API 接口映射 + * - strategy 和 business 共用 comprehensive 接口 + */ +const TAB_API_MAP = { + strategy: "comprehensive", + business: "comprehensive", + valueChain: "valueChain", + development: "keyFactors", +}; + /** * 深度分析组件 * * 功能: - * - 加载深度分析数据(3个接口) + * - 按 Tab 懒加载数据(默认只加载战略分析) + * - 已加载的数据缓存,切换 Tab 不重复请求 * - 管理展开状态 - * - 渲染 DeepAnalysisTab 展示组件 * * @param {Object} props * @param {string} props.stockCode - 股票代码 */ const DeepAnalysis = ({ stockCode }) => { + // 当前 Tab + const [activeTab, setActiveTab] = useState("strategy"); + // 数据状态 const [comprehensiveData, setComprehensiveData] = useState(null); const [valueChainData, setValueChainData] = useState(null); const [keyFactorsData, setKeyFactorsData] = useState(null); - const [loading, setLoading] = useState(false); + + // 各接口独立的 loading 状态 + const [comprehensiveLoading, setComprehensiveLoading] = useState(false); + const [valueChainLoading, setValueChainLoading] = useState(false); + const [keyFactorsLoading, setKeyFactorsLoading] = useState(false); + + // 已加载的接口记录(用于缓存判断) + const loadedApisRef = useRef({ + comprehensive: false, + valueChain: false, + keyFactors: false, + }); // 业务板块展开状态 const [expandedSegments, setExpandedSegments] = useState({}); + // 用于追踪当前 stockCode,避免竞态条件 + const currentStockCodeRef = useRef(stockCode); + // 切换业务板块展开状态 const toggleSegmentExpansion = (segmentIndex) => { setExpandedSegments((prev) => ({ @@ -39,60 +67,144 @@ const DeepAnalysis = ({ stockCode }) => { })); }; - // 加载深度分析数据(3个接口) - const loadDeepAnalysisData = async () => { - if (!stockCode) return; + /** + * 加载指定接口的数据 + */ + const loadApiData = useCallback( + async (apiKey) => { + if (!stockCode) return; - setLoading(true); + // 已加载则跳过 + if (loadedApisRef.current[apiKey]) return; - try { - const requests = [ - fetch( - `${API_BASE_URL}/api/company/comprehensive-analysis/${stockCode}` - ).then((r) => r.json()), - fetch( - `${API_BASE_URL}/api/company/value-chain-analysis/${stockCode}` - ).then((r) => r.json()), - fetch( - `${API_BASE_URL}/api/company/key-factors-timeline/${stockCode}` - ).then((r) => r.json()), - ]; + try { + switch (apiKey) { + case "comprehensive": + setComprehensiveLoading(true); + const comprehensiveRes = await fetch( + `${API_BASE_URL}/api/company/comprehensive-analysis/${stockCode}` + ).then((r) => r.json()); + // 检查 stockCode 是否已变更(防止竞态) + if (currentStockCodeRef.current === stockCode) { + if (comprehensiveRes.success) + setComprehensiveData(comprehensiveRes.data); + loadedApisRef.current.comprehensive = true; + } + break; - const [comprehensiveRes, valueChainRes, keyFactorsRes] = - await Promise.all(requests); + case "valueChain": + setValueChainLoading(true); + const valueChainRes = await fetch( + `${API_BASE_URL}/api/company/value-chain-analysis/${stockCode}` + ).then((r) => r.json()); + if (currentStockCodeRef.current === stockCode) { + if (valueChainRes.success) setValueChainData(valueChainRes.data); + loadedApisRef.current.valueChain = true; + } + break; - if (comprehensiveRes.success) setComprehensiveData(comprehensiveRes.data); - if (valueChainRes.success) setValueChainData(valueChainRes.data); - if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data); - } catch (err) { - logger.error("DeepAnalysis", "loadDeepAnalysisData", err, { stockCode }); - } finally { - setLoading(false); - } - }; + case "keyFactors": + setKeyFactorsLoading(true); + const keyFactorsRes = await fetch( + `${API_BASE_URL}/api/company/key-factors-timeline/${stockCode}` + ).then((r) => r.json()); + if (currentStockCodeRef.current === stockCode) { + if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data); + loadedApisRef.current.keyFactors = true; + } + break; - // stockCode 变更时重新加载数据 + default: + break; + } + } catch (err) { + logger.error("DeepAnalysis", `loadApiData:${apiKey}`, err, { + stockCode, + }); + } finally { + // 清除 loading 状态 + if (apiKey === "comprehensive") setComprehensiveLoading(false); + if (apiKey === "valueChain") setValueChainLoading(false); + if (apiKey === "keyFactors") setKeyFactorsLoading(false); + } + }, + [stockCode] + ); + + /** + * 根据 Tab 加载对应的数据 + */ + const loadTabData = useCallback( + (tabKey) => { + const apiKey = TAB_API_MAP[tabKey]; + if (apiKey) { + loadApiData(apiKey); + } + }, + [loadApiData] + ); + + /** + * Tab 切换回调 + */ + const handleTabChange = useCallback( + (index, tabKey) => { + setActiveTab(tabKey); + loadTabData(tabKey); + }, + [loadTabData] + ); + + // stockCode 变更时重置并加载默认 Tab 数据 useEffect(() => { if (stockCode) { - // 重置数据 + // 更新 ref + currentStockCodeRef.current = stockCode; + + // 重置所有数据和状态 setComprehensiveData(null); setValueChainData(null); setKeyFactorsData(null); setExpandedSegments({}); - // 加载新数据 - loadDeepAnalysisData(); + loadedApisRef.current = { + comprehensive: false, + valueChain: false, + keyFactors: false, + }; + + // 重置为默认 Tab 并加载数据 + setActiveTab("strategy"); + // 加载默认 Tab 的数据 + loadApiData("comprehensive"); } - }, [stockCode]); + }, [stockCode, loadApiData]); + + // 计算当前 Tab 的 loading 状态 + const getCurrentLoading = () => { + const apiKey = TAB_API_MAP[activeTab]; + switch (apiKey) { + case "comprehensive": + return comprehensiveLoading; + case "valueChain": + return valueChainLoading; + case "keyFactors": + return keyFactorsLoading; + default: + return false; + } + }; return ( ); };