// src/views/Company/components/CompanyOverview/index.js // 公司概览主组件 - 状态管理 + Tab 容器 import React, { useState, useEffect } from "react"; import { Box, VStack, HStack, Text, Badge, Card, CardBody, Heading, SimpleGrid, Divider, Spinner, Center, Tabs, TabList, TabPanels, Tab, TabPanel, useColorModeValue, Icon, Grid, GridItem, Stat, StatLabel, StatNumber, Container, Circle, Link, } from "@chakra-ui/react"; import { FaBuilding, FaMapMarkerAlt, FaUserShield, FaBriefcase, FaCalendarAlt, FaGlobe, FaEnvelope, FaPhone, FaCrown, FaBrain, FaInfoCircle, FaNewspaper, } from "react-icons/fa"; import { ExternalLinkIcon } from "@chakra-ui/icons"; 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(); // 格式化工具 const formatUtils = { formatRegisteredCapital: (value) => { if (!value && value !== 0) return "-"; const absValue = Math.abs(value); if (absValue >= 100000) { return (value / 10000).toFixed(2) + "亿元"; } return value.toFixed(2) + "万元"; }, formatDate: (dateString) => { if (!dateString) return "-"; return new Date(dateString).toLocaleDateString("zh-CN"); }, }; // 主组件 const CompanyAnalysisComplete = ({ stockCode: propStockCode }) => { const [stockCode, setStockCode] = useState(propStockCode || "000001"); // 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状态 useEffect(() => { if (propStockCode && propStockCode !== stockCode) { setStockCode(propStockCode); // 重置 Tab 状态 setTabsLoaded({ basicInfo: false, deepAnalysis: false, newsEvents: false }); setActiveTabIndex(0); // 清空深度分析和新闻数据 setComprehensiveData(null); setValueChainData(null); setKeyFactorsData(null); setNewsEvents([]); } }, [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 [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个接口)- 首次加载 const loadBasicInfoData = async () => { if (tabsLoaded.basicInfo) return; setBasicInfoLoading(true); setError(null); try { const requests = [ fetch(`${API_BASE_URL}/api/stock/${stockCode}/basic-info`).then((r) => r.json() ), fetch( `${API_BASE_URL}/api/stock/${stockCode}/actual-control` ).then((r) => r.json()), fetch( `${API_BASE_URL}/api/stock/${stockCode}/concentration` ).then((r) => r.json()), fetch( `${API_BASE_URL}/api/stock/${stockCode}/management?active_only=true` ).then((r) => r.json()), fetch( `${API_BASE_URL}/api/stock/${stockCode}/top-circulation-shareholders?limit=10` ).then((r) => r.json()), fetch( `${API_BASE_URL}/api/stock/${stockCode}/top-shareholders?limit=10` ).then((r) => r.json()), fetch(`${API_BASE_URL}/api/stock/${stockCode}/branches`).then((r) => r.json() ), fetch( `${API_BASE_URL}/api/stock/${stockCode}/announcements?limit=20` ).then((r) => r.json()), fetch( `${API_BASE_URL}/api/stock/${stockCode}/disclosure-schedule` ).then((r) => r.json()), ]; const [ basicRes, actualRes, concentrationRes, managementRes, circulationRes, shareholdersRes, branchesRes, announcementsRes, disclosureRes, ] = await Promise.all(requests); // 设置股票概览数据 if (basicRes.success) setBasicInfo(basicRes.data); if (actualRes.success) setActualControl(actualRes.data); if (concentrationRes.success) setConcentration(concentrationRes.data); if (managementRes.success) setManagement(managementRes.data); if (circulationRes.success) setTopCirculationShareholders(circulationRes.data); if (shareholdersRes.success) setTopShareholders(shareholdersRes.data); if (branchesRes.success) setBranches(branchesRes.data); if (announcementsRes.success) setAnnouncements(announcementsRes.data); if (disclosureRes.success) setDisclosureSchedule(disclosureRes.data); setTabsLoaded((prev) => ({ ...prev, basicInfo: true })); } catch (err) { setError(err.message); logger.error("CompanyOverview", "loadBasicInfoData", err, { stockCode }); } finally { setBasicInfoLoading(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) { 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.legal_representative} 董事长: {basicInfo.chairman} 总经理: {basicInfo.general_manager} 成立日期: {formatUtils.formatDate(basicInfo.establish_date)} {basicInfo.company_intro} 注册资本 {formatUtils.formatRegisteredCapital( basicInfo.reg_capital )} {basicInfo.province} {basicInfo.city} {basicInfo.website} {basicInfo.email} {basicInfo.tel} )} {/* 主要内容区 - 分为基本信息、深度分析和新闻动态 */} 基本信息 深度分析 新闻动态 {/* 基本信息标签页 - 默认 Tab */} {/* 深度分析标签页 - 切换时加载 */} {/* 新闻动态标签页 - 切换时加载 */} ); }; export default CompanyAnalysisComplete;