diff --git a/src/views/Company/components/CompanyOverview/index.js b/src/views/Company/components/CompanyOverview/index.js index a5e3739f..dc765678 100644 --- a/src/views/Company/components/CompanyOverview/index.js +++ b/src/views/Company/components/CompanyOverview/index.js @@ -1,5 +1,5 @@ // src/views/Company/components/CompanyOverview/index.js -// 公司概览主组件 - 状态管理 + Tab 容器 +// 公司概览 - 头部卡片 + 基本信息 import React, { useState, useEffect } from "react"; import { @@ -15,19 +15,12 @@ import { Divider, Spinner, Center, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, - useColorModeValue, Icon, Grid, GridItem, Stat, StatLabel, StatNumber, - Container, Circle, Link, } from "@chakra-ui/react"; @@ -42,9 +35,6 @@ import { FaEnvelope, FaPhone, FaCrown, - FaBrain, - FaInfoCircle, - FaNewspaper, } from "react-icons/fa"; import { ExternalLinkIcon } from "@chakra-ui/icons"; @@ -53,9 +43,7 @@ import { logger } from "@utils/logger"; import { getApiBase } from "@utils/apiConfig"; // 子组件 -import DeepAnalysisTab from "./DeepAnalysisTab"; import BasicInfoTab from "./BasicInfoTab"; -import NewsEventsTab from "./NewsEventsTab"; // API配置 const API_BASE_URL = getApiBase(); @@ -76,87 +64,47 @@ const formatUtils = { }, }; -// 主组件 -const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => { +/** + * 公司概览组件 + * + * 功能: + * - 显示公司头部信息卡片 + * - 显示基本信息(股权结构、管理层、公告等) + * + * @param {Object} props + * @param {string} props.stockCode - 股票代码 + */ +const CompanyOverview = ({ stockCode: propStockCode }) => { const [stockCode, setStockCode] = useState(propStockCode || "000001"); + const [loading, setLoading] = useState(false); + const [dataLoaded, setDataLoaded] = useState(false); - // Tab 懒加载状态追踪 - const [tabsLoaded, setTabsLoaded] = useState({ - basicInfo: false, - deepAnalysis: false, - newsEvents: false, - }); - const [activeTabIndex, setActiveTabIndex] = useState(0); - const [basicInfoLoading, setBasicInfoLoading] = useState(false); - const [deepAnalysisLoading, setDeepAnalysisLoading] = useState(false); - - // 监听props中的stockCode变化 - 重置Tab状态 + // 监听 props 中的 stockCode 变化 useEffect(() => { if (propStockCode && propStockCode !== stockCode) { setStockCode(propStockCode); - // 重置 Tab 状态 - setTabsLoaded({ basicInfo: false, deepAnalysis: false, newsEvents: false }); - setActiveTabIndex(0); - // 清空深度分析和新闻数据 - setComprehensiveData(null); - setValueChainData(null); - setKeyFactorsData(null); - setNewsEvents([]); + setDataLoaded(false); } }, [propStockCode, stockCode]); - // 企业深度分析数据 - const [comprehensiveData, setComprehensiveData] = useState(null); - const [valueChainData, setValueChainData] = useState(null); - const [keyFactorsData, setKeyFactorsData] = useState(null); - - // 股票概览数据 + // 基本信息数据 const [basicInfo, setBasicInfo] = useState(null); const [actualControl, setActualControl] = useState([]); const [concentration, setConcentration] = useState([]); const [management, setManagement] = useState([]); - const [topCirculationShareholders, setTopCirculationShareholders] = useState( - [] - ); + const [topCirculationShareholders, setTopCirculationShareholders] = useState([]); const [topShareholders, setTopShareholders] = useState([]); const [branches, setBranches] = useState([]); const [announcements, setAnnouncements] = useState([]); const [disclosureSchedule, setDisclosureSchedule] = useState([]); - // 新闻动态数据 - const [newsEvents, setNewsEvents] = useState([]); - const [newsLoading, setNewsLoading] = useState(false); - const [newsSearchQuery, setNewsSearchQuery] = useState(""); - const [newsPagination, setNewsPagination] = useState({ - page: 1, - per_page: 10, - total: 0, - pages: 0, - has_next: false, - has_prev: false, - }); - const [_error, setError] = useState(null); - const bgColor = useColorModeValue("gray.50", "gray.900"); - const cardBg = useColorModeValue("white", "gray.800"); - - // 业务板块详情展开状态 - const [expandedSegments, setExpandedSegments] = useState({}); - - // 切换业务板块展开状态 - const toggleSegmentExpansion = (segmentIndex) => { - setExpandedSegments((prev) => ({ - ...prev, - [segmentIndex]: !prev[segmentIndex], - })); - }; - - // 加载基本信息数据(9个接口)- 首次加载 + // 加载基本信息数据(9个接口) const loadBasicInfoData = async () => { - if (tabsLoaded.basicInfo) return; + if (dataLoaded) return; - setBasicInfoLoading(true); + setLoading(true); setError(null); try { @@ -202,7 +150,6 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => { disclosureRes, ] = await Promise.all(requests); - // 设置股票概览数据 if (basicRes.success) setBasicInfo(basicRes.data); if (actualRes.success) setActualControl(actualRes.data); if (concentrationRes.success) setConcentration(concentrationRes.data); @@ -214,388 +161,169 @@ const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => { if (announcementsRes.success) setAnnouncements(announcementsRes.data); if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data); - setTabsLoaded((prev) => ({ ...prev, basicInfo: true })); + setDataLoaded(true); } catch (err) { setError(err.message); logger.error("CompanyOverview", "loadBasicInfoData", err, { stockCode }); } finally { - setBasicInfoLoading(false); + setLoading(false); } }; - // 加载深度分析数据(3个接口)- Tab切换时加载 - const loadDeepAnalysisData = async () => { - if (tabsLoaded.deepAnalysis) return; - - setDeepAnalysisLoading(true); - - 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()), - ]; - - const [comprehensiveRes, valueChainRes, keyFactorsRes] = - await Promise.all(requests); - - // 设置深度分析数据 - if (comprehensiveRes.success) setComprehensiveData(comprehensiveRes.data); - if (valueChainRes.success) setValueChainData(valueChainRes.data); - if (keyFactorsRes.success) setKeyFactorsData(keyFactorsRes.data); - - setTabsLoaded((prev) => ({ ...prev, deepAnalysis: true })); - } catch (err) { - logger.error("CompanyOverview", "loadDeepAnalysisData", err, { - stockCode, - }); - } finally { - setDeepAnalysisLoading(false); - } - }; - - // 首次加载 - 只加载基本信息 + // 首次加载 useEffect(() => { if (stockCode) { loadBasicInfoData(); } }, [stockCode]); - // 加载新闻事件(1个接口)- Tab切换时加载 - const loadNewsEvents = async (page = 1, searchQuery = "") => { - setNewsLoading(true); - try { - const params = new URLSearchParams({ - page: page.toString(), - per_page: "10", - sort: "new", - include_creator: "true", - include_stats: "true", - }); - - const queryText = searchQuery || basicInfo?.SECNAME || ""; - if (queryText) { - params.append("q", queryText); - } - - const response = await fetch( - `${API_BASE_URL}/api/events?${params.toString()}` - ); - const data = await response.json(); - - const events = data.data?.events || data.events || []; - const pagination = data.data?.pagination || { - page: 1, - per_page: 10, - total: 0, - pages: 0, - has_next: false, - has_prev: false, - }; - - setNewsEvents(events); - setNewsPagination(pagination); - - // 首次加载时标记为已加载 - if (page === 1 && !tabsLoaded.newsEvents) { - setTabsLoaded((prev) => ({ ...prev, newsEvents: true })); - } - } catch (err) { - logger.error("CompanyOverview", "loadNewsEvents", err, { - stockCode, - searchQuery, - page, - }); - setNewsEvents([]); - setNewsPagination({ - page: 1, - per_page: 10, - total: 0, - pages: 0, - has_next: false, - has_prev: false, - }); - } finally { - setNewsLoading(false); - } - }; - - // 处理新闻搜索 - const handleNewsSearch = () => { - loadNewsEvents(1, newsSearchQuery); - }; - - // 处理新闻分页 - const handleNewsPageChange = (newPage) => { - loadNewsEvents(newPage, newsSearchQuery); - }; - - // Tab 切换处理 - 懒加载 - const handleTabChange = (index) => { - setActiveTabIndex(index); - // index 0: 基本信息 - 已首次加载 - // index 1: 深度分析 - 切换时加载 - // index 2: 新闻动态 - 切换时加载 - if (index === 1 && !tabsLoaded.deepAnalysis) { - loadDeepAnalysisData(); - } else if (index === 2 && !tabsLoaded.newsEvents && basicInfo) { - loadNewsEvents(1); - } - }; - - if (basicInfoLoading && !basicInfo) { + if (loading && !basicInfo) { return ( - - -
- - - 正在加载企业全景数据... - -
-
-
+
+ + + 正在加载公司概览数据... + +
); } return ( - - - - {/* 公司头部信息 - 醒目展示 */} - {basicInfo && ( - - - - - - - - - - - - - {basicInfo.ORGNAME || basicInfo.SECNAME} - - - {basicInfo.SECCODE} - - - - - {basicInfo.sw_industry_l1} - - - {basicInfo.sw_industry_l2} - - {basicInfo.sw_industry_l3 && ( - - {basicInfo.sw_industry_l3} - - )} - - + + {/* 公司头部信息卡片 */} + {basicInfo && ( + + + + + + + + + + + + + {basicInfo.ORGNAME || basicInfo.SECNAME} + + + {basicInfo.SECCODE} + + + + + {basicInfo.sw_industry_l1} + + + {basicInfo.sw_industry_l2} + + {basicInfo.sw_industry_l3 && ( + + {basicInfo.sw_industry_l3} + + )} - - - - - - - - - 法定代表人: - - - {basicInfo.legal_representative} - - - - - - - - 董事长: - - - {basicInfo.chairman} - - - - - - - - 总经理: - - - {basicInfo.general_manager} - - - - - - - - 成立日期: - - - {formatUtils.formatDate(basicInfo.establish_date)} - - - - - - - - {basicInfo.company_intro} - - - + - - - - 注册资本 - - {formatUtils.formatRegisteredCapital( - basicInfo.reg_capital - )} - - + - + + + + + 法定代表人: + {basicInfo.legal_representative} + + + + + + 董事长: + {basicInfo.chairman} + + + + + + 总经理: + {basicInfo.general_manager} + + + + + + 成立日期: + {formatUtils.formatDate(basicInfo.establish_date)} + + + - - - - - {basicInfo.province} {basicInfo.city} - - - - - - {basicInfo.website} - - - - - {basicInfo.email} - - - - {basicInfo.tel} - - - - - - - - )} + + {basicInfo.company_intro} + + + - {/* 主要内容区 - 分为基本信息、深度分析和新闻动态 */} - - - - - 基本信息 - - - - 深度分析 - - - - 新闻动态 - - + + + + 注册资本 + + {formatUtils.formatRegisteredCapital(basicInfo.reg_capital)} + + - - {/* 基本信息标签页 - 默认 Tab */} - - - + - {/* 深度分析标签页 - 切换时加载 */} - - - + + + + {basicInfo.province} {basicInfo.city} + + + + + {basicInfo.website} + + + + + {basicInfo.email} + + + + {basicInfo.tel} + + + + + + + + )} - {/* 新闻动态标签页 - 切换时加载 */} - - - - - - - - + {/* 基本信息内容 */} + + ); }; -export default CompanyAnalysisComplete; +export default CompanyOverview; diff --git a/src/views/Company/components/CompanyTabs/index.js b/src/views/Company/components/CompanyTabs/index.js index 4f0d3b1b..652f7783 100644 --- a/src/views/Company/components/CompanyTabs/index.js +++ b/src/views/Company/components/CompanyTabs/index.js @@ -9,7 +9,6 @@ import { TabPanels, TabPanel, Divider, - useColorModeValue, } from '@chakra-ui/react'; import TabNavigation from './TabNavigation'; @@ -17,9 +16,11 @@ import { COMPANY_TABS, getTabNameByIndex } from '../../constants'; // 子组件导入(Tab 内容组件) import CompanyOverview from '../CompanyOverview'; +import DeepAnalysis from '../DeepAnalysis'; import MarketDataView from '../MarketDataView'; import FinancialPanorama from '../FinancialPanorama'; import ForecastReport from '../ForecastReport'; +import DynamicTracking from '../DynamicTracking'; /** * Tab 组件映射 @@ -27,9 +28,11 @@ import ForecastReport from '../ForecastReport'; */ const TAB_COMPONENTS = { overview: CompanyOverview, + analysis: DeepAnalysis, market: MarketDataView, financial: FinancialPanorama, forecast: ForecastReport, + tracking: DynamicTracking, }; /** @@ -48,10 +51,6 @@ const TAB_COMPONENTS = { const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => { const [currentIndex, setCurrentIndex] = useState(0); - // 主题相关颜色 - const tabBg = useColorModeValue('gray.50', 'gray.700'); - const activeBg = useColorModeValue('blue.500', 'blue.400'); - /** * 处理 Tab 切换 */ @@ -76,7 +75,7 @@ const CompanyTabs = ({ stockCode, onTabChange, bgColor }) => { onChange={handleTabChange} > {/* Tab 导航 */} - + diff --git a/src/views/Company/components/DeepAnalysis/index.js b/src/views/Company/components/DeepAnalysis/index.js new file mode 100644 index 00000000..77a3dca4 --- /dev/null +++ b/src/views/Company/components/DeepAnalysis/index.js @@ -0,0 +1,100 @@ +// src/views/Company/components/DeepAnalysis/index.js +// 深度分析 - 独立一级 Tab 组件 + +import React, { useState, useEffect } from "react"; +import { logger } from "@utils/logger"; +import { getApiBase } from "@utils/apiConfig"; + +// 复用原有的展示组件 +import DeepAnalysisTab from "../CompanyOverview/DeepAnalysisTab"; + +const API_BASE_URL = getApiBase(); + +/** + * 深度分析组件 + * + * 功能: + * - 加载深度分析数据(3个接口) + * - 管理展开状态 + * - 渲染 DeepAnalysisTab 展示组件 + * + * @param {Object} props + * @param {string} props.stockCode - 股票代码 + */ +const DeepAnalysis = ({ stockCode }) => { + // 数据状态 + const [comprehensiveData, setComprehensiveData] = useState(null); + const [valueChainData, setValueChainData] = useState(null); + const [keyFactorsData, setKeyFactorsData] = useState(null); + const [loading, setLoading] = useState(false); + + // 业务板块展开状态 + const [expandedSegments, setExpandedSegments] = useState({}); + + // 切换业务板块展开状态 + const toggleSegmentExpansion = (segmentIndex) => { + setExpandedSegments((prev) => ({ + ...prev, + [segmentIndex]: !prev[segmentIndex], + })); + }; + + // 加载深度分析数据(3个接口) + const loadDeepAnalysisData = async () => { + if (!stockCode) return; + + setLoading(true); + + 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()), + ]; + + const [comprehensiveRes, valueChainRes, keyFactorsRes] = + await Promise.all(requests); + + 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); + } + }; + + // stockCode 变更时重新加载数据 + useEffect(() => { + if (stockCode) { + // 重置数据 + setComprehensiveData(null); + setValueChainData(null); + setKeyFactorsData(null); + setExpandedSegments({}); + // 加载新数据 + loadDeepAnalysisData(); + } + }, [stockCode]); + + return ( + + ); +}; + +export default DeepAnalysis; diff --git a/src/views/Company/components/DynamicTracking/index.js b/src/views/Company/components/DynamicTracking/index.js new file mode 100644 index 00000000..77cd70ec --- /dev/null +++ b/src/views/Company/components/DynamicTracking/index.js @@ -0,0 +1,46 @@ +// src/views/Company/components/DynamicTracking/index.js +// 动态跟踪 - 独立一级 Tab 组件 + +import React from "react"; +import { + Box, + VStack, + Text, + Icon, + Card, + CardBody, +} from "@chakra-ui/react"; +import { FaNewspaper } from "react-icons/fa"; + +/** + * 动态跟踪组件 + * + * 功能: + * - 预留二级 Tab 结构 + * - 后续放入新闻动态等 + * + * @param {Object} props + * @param {string} props.stockCode - 股票代码 + */ +const DynamicTracking = ({ stockCode }) => { + return ( + + + + + + 动态跟踪 + + + 后续将添加新闻动态等内容 + + + 股票代码: {stockCode} + + + + + ); +}; + +export default DynamicTracking; diff --git a/src/views/Company/constants/index.js b/src/views/Company/constants/index.js index 078ae702..86931ecb 100644 --- a/src/views/Company/constants/index.js +++ b/src/views/Company/constants/index.js @@ -1,7 +1,7 @@ // src/views/Company/constants/index.js // 公司详情页面常量配置 -import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa'; +import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle, FaBrain, FaNewspaper } from 'react-icons/fa'; /** * Tab 配置 @@ -9,9 +9,11 @@ import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-ic */ export const COMPANY_TABS = [ { key: 'overview', name: '公司概览', icon: FaInfoCircle }, + { key: 'analysis', name: '深度分析', icon: FaBrain }, { key: 'market', name: '股票行情', icon: FaChartLine }, { key: 'financial', name: '财务全景', icon: FaMoneyBillWave }, { key: 'forecast', name: '盈利预测', icon: FaChartBar }, + { key: 'tracking', name: '动态跟踪', icon: FaNewspaper }, ]; /** diff --git a/src/views/Company/index.js b/src/views/Company/index.js index 3c947f51..bd40e48a 100644 --- a/src/views/Company/index.js +++ b/src/views/Company/index.js @@ -2,7 +2,7 @@ // 公司详情页面入口 - 纯组合层 import React, { useEffect, useRef } from 'react'; -import { Container, VStack, useColorModeValue } from '@chakra-ui/react'; +import { Container, VStack } from '@chakra-ui/react'; // 自定义 Hooks import { useCompanyStock } from './hooks/useCompanyStock'; @@ -24,8 +24,6 @@ import CompanyTabs from './components/CompanyTabs'; * - PostHog 事件追踪 */ const CompanyIndex = () => { - const bgColor = useColorModeValue('white', 'gray.800'); - // 1. 先获取股票代码(不带追踪回调) const { stockCode, @@ -78,7 +76,7 @@ const CompanyIndex = () => { isInWatchlist={isInWatchlist} isWatchlistLoading={isWatchlistLoading} onWatchlistToggle={handleWatchlistToggle} - bgColor={bgColor} + bgColor="white" /> {/* 股票行情卡片:价格、关键指标、主力动态 */} @@ -88,7 +86,7 @@ const CompanyIndex = () => {